mirror of
https://github.com/cryptoadvance/specter-desktop.git
synced 2026-08-13 12:33:29 +02:00
* Ci improvements (#103) * pin bitcoin to new v0.21.1 tag * fix binary installation * for binary install, pytest.ini is the file to use * kick * adjusted Dockerfile to have at least wget to download artifacts * investigate test_hwibridge timing out * restest with v0.20.1 * implements timeout, hope that was reason to hang * remove livelogging again * fix hanging test * Enable bitcoind-log-output on demand * Timeouts now raising SpecterErrors * catch a bit more generic Exception * increase timeout * fix abandontx test * simplified code * increase timeout * increased timeout also for pytest * separate timeouts for liquid/bitcoin * forget checkin * increase timeout for bitcoin as well * bugfix: node specific timeouts * increase timeout for elm hotwallet * Now timeouts for all occurences of broadcasting * global vars done right * Yet another ReadTimeoutError to take account for * fix assertion * fix not-defined issue * proper status_code checking instead of error_code * increase wait-time * increase btc:mine wait to 15 s Co-authored-by: Stepan Snigirev <snigirev.stepan@gmail.com> * rollback to v0.20.1 * rollback - it is a src-install, stupid * mixed up bitcoin and elements * forgot to fix reference * rollback test_abandon_purged_tx * increase timeout to 30 s * increase Liquid on cypress to 40s Co-authored-by: Stepan Snigirev <snigirev.stepan@gmail.com>
This commit is contained in:
parent
b4e3e2689e
commit
cb8968b5fb
20 changed files with 305 additions and 184 deletions
11
.cirrus.yml
11
.cirrus.yml
|
|
@ -52,16 +52,19 @@ test_task:
|
|||
- apt-get update && apt-get install -y --no-install-recommends python3-dev python3-pip wget
|
||||
<< : *PREP_STUFF_TEMPLATE
|
||||
test_script:
|
||||
- pwd
|
||||
- ls -l ./.env/bin
|
||||
- source ./.env/bin/activate
|
||||
- echo $PATH
|
||||
#- pip3 install -e .
|
||||
- pytest --cov=cryptoadvance
|
||||
- pytest --cov=cryptoadvance --junitxml=./testresults.xml
|
||||
always:
|
||||
junit_artifacts:
|
||||
path: "./testresults.xml"
|
||||
format: junit
|
||||
|
||||
|
||||
cypress_test_task:
|
||||
container:
|
||||
image: registry.gitlab.com/cryptoadvance/specter-desktop/cypress-python:focal
|
||||
image: registry.gitlab.com/cryptoadvance/specter-desktop/cypress-python:latest
|
||||
pre_prep_script:
|
||||
- apt-get update && apt-get install -y --no-install-recommends python3-dev python3-pip python3-virtualenv bc
|
||||
# The stupid old debian-package is not installing a proper binary but just the python-package
|
||||
|
|
|
|||
|
|
@ -172,6 +172,13 @@ pytest tests/test_specter.py::test_specter
|
|||
pytest --setup-show
|
||||
```
|
||||
|
||||
Get the log-output of bitcoind side by side with the test-output. For sure you will only see the logs if the test fails.
|
||||
```
|
||||
pytest --bitcoind-log-stdout
|
||||
# Probably better to redirect into a file
|
||||
pytest --bitcoind-log-stdout > testoutput.log
|
||||
```
|
||||
|
||||
Check the cypress-section on how to run cypress-frontend-tests.
|
||||
|
||||
## Code-Style
|
||||
|
|
|
|||
|
|
@ -10,5 +10,8 @@
|
|||
"spec_elm_single_segwit_wallet.js",
|
||||
"spec_elm_multi_segwit_wallet.js"
|
||||
],
|
||||
"baseUrl": "http://localhost:25444"
|
||||
"baseUrl": "http://localhost:25444",
|
||||
"env": {
|
||||
"broadcast_timeout":"8000"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
describe('Operating with an elements multisig wallet', () => {
|
||||
// 4000ms was often not enough for waiting a elm-transaction
|
||||
// So let's try double as much:
|
||||
const broadcast_timeout = 8000
|
||||
|
||||
|
||||
it('Creates an two elements multisig hot wallets (segwit/nested) both 2/3', () => {
|
||||
cy.viewport(1200,660)
|
||||
|
|
@ -84,7 +82,7 @@ describe('Operating with an elements multisig wallet', () => {
|
|||
cy.get('#broadcast_local_btn').click()
|
||||
// gets redirected to "transactions"
|
||||
|
||||
cy.get('#fullbalance_amount', { timeout: broadcast_timeout })
|
||||
cy.get('#fullbalance_amount', { timeout: Cypress.env("broadcast_timeout") })
|
||||
.should(($div) => {
|
||||
const newBalance = parseFloat($div.text())
|
||||
expect(newBalance).to.be.lte(oldBalance - 1.5)
|
||||
|
|
@ -127,7 +125,7 @@ describe('Operating with an elements multisig wallet', () => {
|
|||
cy.get('#broadcast_local_btn').click()
|
||||
// gets redirected to "transactions"
|
||||
|
||||
cy.get('#fullbalance_amount', { timeout: broadcast_timeout })
|
||||
cy.get('#fullbalance_amount', { timeout: Cypress.env("broadcast_timeout") })
|
||||
.should(($div) => {
|
||||
const newBalance = parseFloat($div.text())
|
||||
expect(newBalance).to.be.lte(oldBalance - 1.5)
|
||||
|
|
@ -169,7 +167,7 @@ describe('Operating with an elements multisig wallet', () => {
|
|||
cy.get('#broadcast_local_btn').click()
|
||||
// gets redirected to "transactions"
|
||||
|
||||
cy.get('#fullbalance_amount', { timeout: broadcast_timeout })
|
||||
cy.get('#fullbalance_amount', { timeout: Cypress.env("broadcast_timeout") })
|
||||
.should(($div) => {
|
||||
const newBalance = parseFloat($div.text())
|
||||
expect(newBalance).to.be.lte(oldBalance - 1.5)
|
||||
|
|
@ -211,7 +209,7 @@ describe('Operating with an elements multisig wallet', () => {
|
|||
cy.get('#send_tx_btn').click()
|
||||
cy.get('#broadcast_local_btn').click()
|
||||
// gets redirected to "transactions"
|
||||
cy.get('#fullbalance_amount', { timeout: broadcast_timeout})
|
||||
cy.get('#fullbalance_amount', { timeout: Cypress.env("broadcast_timeout") })
|
||||
.should(($div) => {
|
||||
const newBalance = parseFloat($div.text())
|
||||
expect(newBalance).to.be.lte(oldBalance - 1.5)
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ describe('Operating with an elements singlesig wallet', () => {
|
|||
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')
|
||||
cy.get('#fullbalance_amount', { timeout: Cypress.env("broadcast_timeout") })
|
||||
.should(($div) => {
|
||||
const newBalance = parseFloat($div.text())
|
||||
expect(newBalance).to.be.lte(oldBalance - 1.5)
|
||||
|
|
@ -56,7 +56,7 @@ describe('Operating with an elements singlesig wallet', () => {
|
|||
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')
|
||||
cy.get('#fullbalance_amount', { timeout: Cypress.env("broadcast_timeout") })
|
||||
.should(($div) => {
|
||||
const newBalance = parseFloat($div.text())
|
||||
expect(newBalance).to.be.lte(oldBalance - 1.5)
|
||||
|
|
@ -77,7 +77,7 @@ describe('Operating with an elements singlesig wallet', () => {
|
|||
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')
|
||||
cy.get('#fullbalance_amount', { timeout: Cypress.env("broadcast_timeout") })
|
||||
.should(($div) => {
|
||||
const newBalance = parseFloat($div.text())
|
||||
expect(newBalance).to.be.lte(oldBalance - 1.5)
|
||||
|
|
@ -102,7 +102,7 @@ describe('Operating with an elements singlesig wallet', () => {
|
|||
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')
|
||||
cy.get('#fullbalance_amount', { timeout: Cypress.env("broadcast_timeout") })
|
||||
.should(($div) => {
|
||||
const newBalance = parseFloat($div.text())
|
||||
expect(newBalance).to.be.lte(oldBalance - 1.5)
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ describe('Send transactions from bitcoin hotwallets', () => {
|
|||
cy.get('#hot_device_1_hot_sign_btn').click()
|
||||
cy.get('#hot_enter_passphrase__submit').click()
|
||||
cy.get('#broadcast_local_btn').click()
|
||||
cy.get('#fullbalance_amount')
|
||||
cy.get('#fullbalance_amount', { timeout: Cypress.env("broadcast_timeout") })
|
||||
.should(($div) => {
|
||||
const n = parseFloat($div.text())
|
||||
expect(n).to.be.equals(0)
|
||||
|
|
@ -78,7 +78,7 @@ describe('Send transactions from bitcoin hotwallets', () => {
|
|||
cy.get('#hot_device_1_hot_sign_btn').click()
|
||||
cy.get('#hot_enter_passphrase__submit').click()
|
||||
cy.get('#broadcast_local_btn').click()
|
||||
cy.get('#fullbalance_amount')
|
||||
cy.get('#fullbalance_amount', { timeout: Cypress.env("broadcast_timeout") })
|
||||
.should(($div) => {
|
||||
const n = parseFloat($div.text())
|
||||
expect(n).to.be.equals(0)
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ Cypress.Commands.add("addHotDevice", (name, node_type) => {
|
|||
cy.get('#submit-mnemonic').click()
|
||||
cy.get('#device_name').type(name)
|
||||
cy.get('#submit-keys').click()
|
||||
cy.get('#devices_list > .item > div').contains(name)
|
||||
cy.get('#devices_list > .item > div', { timeout: 8000 }).contains(name)
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -136,7 +136,7 @@ Cypress.Commands.add("mine2wallet", (chain) => {
|
|||
}
|
||||
cy.wait(15000)
|
||||
cy.reload()
|
||||
cy.get('#fullbalance_amount')
|
||||
cy.get('#fullbalance_amount') // Wait 5 secs + 15 secs timeout
|
||||
.should(($div) => {
|
||||
const n = parseFloat($div.text())
|
||||
expect(n).to.be.gt(oldBalance)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ FROM registry.gitlab.com/cryptoadvance/specter-desktop/cypress-base-ubuntu-focal
|
|||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
python3-pip python3-virtualenv zip unzip file apt libusb-1.0-0-dev libudev-dev \
|
||||
bc libevent-2.1-7
|
||||
bc libevent-2.1-7 jq wget curl
|
||||
|
||||
# Stuff needed for Elements (compilation)
|
||||
RUN DEBIAN_FRONTEND="noninteractive" apt-get install --no-install-recommends -y bsdmainutils libboost-test-dev libboost-filesystem-dev libboost-thread-dev libsqlite3-dev git libevent-pthreads-2.1-7
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
[pytest]
|
||||
norecursedirs = tests/bitcoin tests/elements
|
||||
norecursedirs = tests/bitcoin* tests/elements
|
||||
log_format = [%(levelname)8s] %(message)s %(name)s (%(filename)s:%(lineno)s)
|
||||
addopts = --bitcoind-version v0.20.1 --elementsd-version v0.20.99
|
||||
markers =
|
||||
|
|
|
|||
|
|
@ -62,6 +62,10 @@ class BaseConfig(object):
|
|||
"ELMD_REGTEST_DATA_DIR", "/tmp/specter_elm_regtest_plain_datadir"
|
||||
)
|
||||
|
||||
# the default timeout for Bitcoin/Liquid RPC-calls
|
||||
BITCOIN_RPC_TIMEOUT = int(os.getenv("BITCOIN_RPC_TIMEOUT", "10"))
|
||||
LIQUID_RPC_TIMEOUT = int(os.getenv("LIQUID_RPC_TIMEOUT", "10"))
|
||||
|
||||
# The self-signed ssl-certificate which is lazily created is configurable to a certain extent
|
||||
SPECTER_SSL_CERT_SUBJECT_C = os.getenv("SPECTER_SSL_CERT_SUBJECT_C", "DE")
|
||||
SPECTER_SSL_CERT_SUBJECT_ST = os.getenv("SPECTER_SSL_CERT_SUBJECT_ST", "BDW")
|
||||
|
|
@ -148,6 +152,10 @@ class TestConfig(BaseConfig):
|
|||
# API active by default in test-mode
|
||||
SPECTER_API_ACTIVE = _get_bool_env_var("SPECTER_API_ACTIVE", "True")
|
||||
|
||||
# See #1316 since Bitcoin v0.21.1 (not only) the importmulti-call takes longer than 10 seconds on cirrus
|
||||
BITCOIN_RPC_TIMEOUT = 20
|
||||
LIQUID_RPC_TIMEOUT = 30
|
||||
|
||||
|
||||
class CypressTestConfig(TestConfig):
|
||||
SPECTER_DATA_FOLDER = os.path.expanduser(
|
||||
|
|
@ -165,6 +173,8 @@ class CypressTestConfig(TestConfig):
|
|||
BTCD_REGTEST_DATA_DIR = os.getenv(
|
||||
"BTCD_REGTEST_DATA_DIR", "/tmp/specter_cypress_elm_regtest_plain_datadir"
|
||||
)
|
||||
BITCOIN_RPC_TIMEOUT = 30
|
||||
LIQUID_RPC_TIMEOUT = 40
|
||||
|
||||
|
||||
class ProductionConfig(BaseConfig):
|
||||
|
|
|
|||
|
|
@ -561,7 +561,7 @@ class LiquidRPC(BitcoinRPC):
|
|||
port=rpc.port,
|
||||
protocol=rpc.protocol,
|
||||
path=rpc.path,
|
||||
timeout=rpc.timeout,
|
||||
timeout=cls.default_timeout, # Elements is slower
|
||||
session=rpc.session,
|
||||
proxy_url=rpc.proxy_url,
|
||||
only_tor=rpc.only_tor,
|
||||
|
|
|
|||
|
|
@ -172,8 +172,14 @@ class Node:
|
|||
if is_liquid(res.get("chain")):
|
||||
# convert to LiquidRPC class
|
||||
rpc = LiquidRPC.from_bitcoin_rpc(rpc)
|
||||
except RpcError as rpce:
|
||||
if rpce.status_code == 401:
|
||||
return rpc # The user is failing to configure correctly
|
||||
logger.exception(rpce)
|
||||
return None
|
||||
except Exception as e:
|
||||
return rpc
|
||||
logger.exception(e)
|
||||
return None
|
||||
if rpc.test_connection():
|
||||
return rpc
|
||||
else:
|
||||
|
|
@ -335,20 +341,21 @@ class Node:
|
|||
rpc.listwallets()
|
||||
r["tests"]["wallets"] = True
|
||||
except RpcError as rpce:
|
||||
logger.error(rpce)
|
||||
logger.info(f"Couldn't list wallets while test_rpc {rpce}")
|
||||
r["tests"]["wallets"] = False
|
||||
r["err"] = "Wallets disabled"
|
||||
|
||||
r["out"] = json.dumps(rpc.getblockchaininfo(), indent=4)
|
||||
except ConnectionError as e:
|
||||
logger.error("Caught an ConnectionError while test_rpc: %s", e)
|
||||
logger.info("Caught an ConnectionError while test_rpc: %s", e)
|
||||
|
||||
r["tests"]["connectable"] = False
|
||||
r["err"] = _("Failed to connect!")
|
||||
r["code"] = -1
|
||||
except RpcError as rpce:
|
||||
logger.error("Caught an RpcError while test_rpc: %s", rpce)
|
||||
logger.error(rpce.status_code)
|
||||
logger.info(
|
||||
f"Caught an RpcError while test_rpc status_code: {rpce.status_code} error_code:{rpce.error_code}"
|
||||
)
|
||||
r["tests"]["connectable"] = True
|
||||
r["code"] = rpc.r.status_code
|
||||
if rpce.status_code == 401:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import logging
|
||||
import requests, json, os
|
||||
import requests, urllib3, json, os
|
||||
import os, sys, errno
|
||||
from .helpers import is_ip_private
|
||||
from .specter_error import SpecterError
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -189,6 +190,7 @@ class RpcError(Exception):
|
|||
try:
|
||||
rpc.does_not_exist()
|
||||
except RpcError as rpce:
|
||||
assert rpce.status_code == 401 # A https-status-code
|
||||
assert rpce.error_code == -32601
|
||||
assert rpce.error_msg == "Method not found"
|
||||
See for error_codes https://github.com/bitcoin/bitcoin/blob/v0.15.0.1/src/rpc/protocol.h#L32L87
|
||||
|
|
@ -214,9 +216,14 @@ class RpcError(Exception):
|
|||
class BitcoinRPC:
|
||||
counter = 0
|
||||
|
||||
# These are used for tracing the calls without too many duplicates
|
||||
last_call_hash = None
|
||||
last_call_hash_counter = 0
|
||||
|
||||
# https://docs.python-requests.org/en/master/user/quickstart/#timeouts
|
||||
# None means until connection closes. It's specified in seconds
|
||||
default_timeout = None # seconds
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
user="bitcoin",
|
||||
|
|
@ -238,7 +245,7 @@ class BitcoinRPC:
|
|||
self.protocol = protocol
|
||||
self.host = host
|
||||
self.path = path
|
||||
self.timeout = timeout
|
||||
self.timeout = timeout or self.__class__.default_timeout
|
||||
self.proxy_url = proxy_url
|
||||
self.only_tor = only_tor
|
||||
self.r = None
|
||||
|
|
@ -338,9 +345,25 @@ class BitcoinRPC:
|
|||
if "wallet" in kwargs:
|
||||
url = url + "/wallet/{}".format(kwargs["wallet"])
|
||||
self.trace_call(url, payload)
|
||||
r = self.session.post(
|
||||
url, data=json.dumps(payload), headers=headers, timeout=timeout
|
||||
)
|
||||
try:
|
||||
r = self.session.post(
|
||||
url, data=json.dumps(payload), headers=headers, timeout=timeout
|
||||
)
|
||||
except (requests.exceptions.Timeout, urllib3.exceptions.ReadTimeoutError) as to:
|
||||
# Timeout is effectively one of the two:
|
||||
# ConnectTimeout: The request timed out while trying to connect to the remote server
|
||||
# ReadTimeout: The server did not send any data in the allotted amount of time.
|
||||
# ReadTimeoutError: Raised when a socket timeout occurs while receiving data from a server
|
||||
logger.error(
|
||||
"Timeout while {} call({: <28}) payload:{} Exception: {}".format(
|
||||
self.__class__.__name__, "/".join(url.split("/")[3:]), payload, to
|
||||
)
|
||||
)
|
||||
raise SpecterError(
|
||||
"Timeout while {} call({: <28}) payload:{}".format(
|
||||
self.__class__.__name__, "/".join(url.split("/")[3:]), payload
|
||||
)
|
||||
)
|
||||
self.r = r
|
||||
if r.status_code != 200:
|
||||
logger.debug(f"last call FAILED: {r.text} (raising RpcError)")
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ from flask import Flask, redirect, request, url_for, jsonify, session
|
|||
from flask_babel import Babel
|
||||
from flask_login import LoginManager, login_user
|
||||
from flask_wtf.csrf import CSRFProtect
|
||||
from cryptoadvance.specter.liquid.rpc import LiquidRPC
|
||||
|
||||
from cryptoadvance.specter.rpc import BitcoinRPC
|
||||
|
||||
from .helpers import hwi_get_config
|
||||
from .specter import Specter
|
||||
|
|
@ -104,6 +107,9 @@ def init_app(app, hwibridge=False, specter=None):
|
|||
# Login via Flask-Login
|
||||
app.logger.info("Initializing LoginManager")
|
||||
app.secret_key = app.config["SECRET_KEY"]
|
||||
BitcoinRPC.default_timeout = app.config["BITCOIN_RPC_TIMEOUT"]
|
||||
LiquidRPC.default_timeout = app.config["LIQUID_RPC_TIMEOUT"]
|
||||
|
||||
if specter is None:
|
||||
# the default. If not None, then it got injected for testing
|
||||
app.logger.info("Initializing Specter")
|
||||
|
|
|
|||
7
src/cryptoadvance/specter/util/common.py
Normal file
7
src/cryptoadvance/specter/util/common.py
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
def str2bool(my_str):
|
||||
"""returns a reasonable boolean from a string so that "False" will result in False"""
|
||||
if my_str is None:
|
||||
return False
|
||||
elif isinstance(my_str, str) and my_str.lower() == "false":
|
||||
return False
|
||||
return bool(my_str)
|
||||
|
|
@ -71,7 +71,11 @@ def create_lots_of_big_transactions(wallet, txouts, utxos, num, fee):
|
|||
for txout in txouts:
|
||||
tx.vout.append(txout)
|
||||
newtx = tx.serialize().hex()
|
||||
signresult = wallet.devices[0].sign_raw_tx(newtx, wallet)
|
||||
txid = node.sendrawtransaction(signresult["hex"], 0)
|
||||
psbtF = wallet.rpc.converttopsbt(newtx)
|
||||
psbtFF = wallet.rpc.walletprocesspsbt(psbtF)
|
||||
signed = wallet.devices[0].sign_psbt(psbtFF["psbt"], wallet)
|
||||
assert signed["complete"]
|
||||
finalized = wallet.rpc.finalizepsbt(signed["psbt"])
|
||||
txid = node.sendrawtransaction(finalized["hex"])
|
||||
txids.append(txid)
|
||||
return txids
|
||||
|
|
|
|||
|
|
@ -24,13 +24,37 @@ from cryptoadvance.specter.server import create_app, init_app
|
|||
from cryptoadvance.specter.specter import Specter
|
||||
from cryptoadvance.specter.user import User
|
||||
from cryptoadvance.specter.util.wallet_importer import WalletImporter
|
||||
from cryptoadvance.specter.util.common import str2bool
|
||||
import code, traceback, signal
|
||||
|
||||
pytest_plugins = ["ghost_machine"]
|
||||
|
||||
# This is from https://stackoverflow.com/questions/132058/showing-the-stack-trace-from-a-running-python-application
|
||||
# it enables stopping a hanging test via sending the pytest-process a SIGUSR2 (12)
|
||||
# kill 12 pid-of-pytest
|
||||
# In the article they claim to open a debug-console which didn't work for me but at least
|
||||
# you get a stacktrace in the output.
|
||||
def debug(sig, frame):
|
||||
"""Interrupt running process, and provide a python prompt for
|
||||
interactive debugging."""
|
||||
d = {"_frame": frame} # Allow access to frame object.
|
||||
d.update(frame.f_globals) # Unless shadowed by global
|
||||
d.update(frame.f_locals)
|
||||
|
||||
i = code.InteractiveConsole(d)
|
||||
message = "Signal received : entering python shell.\nTraceback:\n"
|
||||
message += "".join(traceback.format_stack(frame))
|
||||
i.interact(message)
|
||||
|
||||
|
||||
def listen():
|
||||
signal.signal(signal.SIGUSR2, debug) # Register handler
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
"""Internally called to add options to pytest
|
||||
see pytest_generate_tests(metafunc) on how to check that
|
||||
Also used to register the SIGUSR2 (12) as decribed in conftest.py
|
||||
"""
|
||||
parser.addoption("--docker", action="store_true", help="run bitcoind in docker")
|
||||
parser.addoption(
|
||||
|
|
@ -39,12 +63,19 @@ def pytest_addoption(parser):
|
|||
default="v0.20.1",
|
||||
help="Version of bitcoind (something which works with git checkout ...)",
|
||||
)
|
||||
parser.addoption(
|
||||
"--bitcoind-log-stdout",
|
||||
action="store",
|
||||
default=False,
|
||||
help="Whether bitcoind should log to stdout (default:False)",
|
||||
)
|
||||
parser.addoption(
|
||||
"--elementsd-version",
|
||||
action="store",
|
||||
default="master",
|
||||
help="Version of elementsd (something which works with git checkout ...)",
|
||||
)
|
||||
listen()
|
||||
|
||||
|
||||
def pytest_generate_tests(metafunc):
|
||||
|
|
@ -61,6 +92,7 @@ def pytest_generate_tests(metafunc):
|
|||
def instantiate_bitcoind_controller(docker, request, rpcport=18543, extra_args=[]):
|
||||
# logging.getLogger().setLevel(logging.DEBUG)
|
||||
requested_version = request.config.getoption("--bitcoind-version")
|
||||
log_stdout = str2bool(request.config.getoption("--bitcoind-log-stdout"))
|
||||
if docker:
|
||||
from cryptoadvance.specter.process_controller.bitcoind_docker_controller import (
|
||||
BitcoindDockerController,
|
||||
|
|
@ -83,7 +115,10 @@ def instantiate_bitcoind_controller(docker, request, rpcport=18543, extra_args=[
|
|||
rpcport=rpcport
|
||||
) # Alternatively take the one on the path for now
|
||||
bitcoind_controller.start_bitcoind(
|
||||
cleanup_at_exit=True, cleanup_hard=True, extra_args=extra_args
|
||||
cleanup_at_exit=True,
|
||||
cleanup_hard=True,
|
||||
extra_args=extra_args,
|
||||
log_stdout=log_stdout,
|
||||
)
|
||||
assert not bitcoind_controller.datadir is None
|
||||
running_version = bitcoind_controller.version()
|
||||
|
|
@ -367,6 +402,7 @@ def specter_regtest_configured(bitcoin_regtest, devices_filled_data_folder):
|
|||
bitcoin_regtest.mine()
|
||||
# Realize that the wallet has funds:
|
||||
wallet.update()
|
||||
assert wallet.fullbalance >= 20
|
||||
assert not specter.wallet_manager.working_folder is None
|
||||
try:
|
||||
yield specter
|
||||
|
|
|
|||
|
|
@ -73,9 +73,11 @@ def test_write_device(app):
|
|||
"original": "tpubDDZ5jjGT5RvrAyjoLZfdCfv1PAPmicnhNctwZGKiCMF1Zy5hCGMqppxwYZzWgvPqk7LucMMHo7rkB6Dyj5ZLd2W62FAEP3U6pV4jD5gb9ma"
|
||||
}
|
||||
)
|
||||
app.specter.device_manager.add_device("some_name", "the_type", [a_key, another_key])
|
||||
app.specter.device_manager.add_device(
|
||||
"some_name2", "the_type", [a_key, another_key]
|
||||
)
|
||||
write_device(
|
||||
app.specter.device_manager.get_by_alias("some_name"),
|
||||
app.specter.device_manager.get_by_alias("some_name2"),
|
||||
"/tmp/delete_me_test_file.json",
|
||||
)
|
||||
os.remove("/tmp/delete_me_test_file.json")
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import pytest
|
||||
|
||||
from cryptoadvance.specter.rpc import BitcoinRPC, RpcError
|
||||
from cryptoadvance.specter.specter_error import SpecterError
|
||||
|
||||
|
||||
def test_BitcoinRpc(bitcoin_regtest):
|
||||
|
|
@ -23,3 +24,22 @@ def test_BitcoinRpc(bitcoin_regtest):
|
|||
except RpcError as rpce:
|
||||
assert rpce.error_code == -32601
|
||||
assert rpce.error_msg == "Method not found"
|
||||
|
||||
|
||||
def test_BitcoinRpc_timeout(bitcoin_regtest, caplog):
|
||||
brt = bitcoin_regtest # stupid long name
|
||||
rpc = BitcoinRPC(
|
||||
brt.rpcconn.rpcuser,
|
||||
brt.rpcconn.rpcpassword,
|
||||
host=brt.rpcconn.ipaddress,
|
||||
port=brt.rpcconn.rpcport,
|
||||
)
|
||||
rpc.timeout = 0.0000000000001
|
||||
try:
|
||||
rpc.createwallet("some_test_wallet_name_392")
|
||||
assert False, "Should raise an exception"
|
||||
except SpecterError:
|
||||
assert (
|
||||
"Timeout while BitcoinRPC call( ) payload:[{'method': 'createwallet', 'params': ['some_test_wallet_name_392'], 'jsonrpc': '2.0', 'id': 0}]"
|
||||
in caplog.text
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import json, logging, pytest, time, os
|
||||
import json, logging, pytest
|
||||
from decimal import Decimal
|
||||
from cryptoadvance.specter.helpers import alias, generate_mnemonic
|
||||
from cryptoadvance.specter.key import Key
|
||||
|
|
@ -6,7 +6,6 @@ from cryptoadvance.specter.rpc import BitcoinRPC
|
|||
from cryptoadvance.specter.specter import Specter
|
||||
from cryptoadvance.specter.specter_error import SpecterError
|
||||
from cryptoadvance.specter.managers.wallet_manager import WalletManager
|
||||
from conftest import instantiate_bitcoind_controller
|
||||
|
||||
|
||||
def test_alias():
|
||||
|
|
@ -36,6 +35,7 @@ def test_abandon_purged_tx(
|
|||
# from the mempool. Test starts a new bitcoind with a restricted mempool to make it
|
||||
# easier to spam the mempool and purge our target tx.
|
||||
# TODO: Similar test but for maxmempoolexpiry?
|
||||
|
||||
# Copied and adapted from:
|
||||
# https://github.com/bitcoin/bitcoin/blob/master/test/functional/mempool_limit.py
|
||||
from bitcoin_core.test.functional.test_framework.util import (
|
||||
|
|
@ -43,6 +43,7 @@ def test_abandon_purged_tx(
|
|||
satoshi_round,
|
||||
create_lots_of_big_transactions,
|
||||
)
|
||||
from conftest import instantiate_bitcoind_controller
|
||||
|
||||
caplog.set_level(logging.DEBUG)
|
||||
|
||||
|
|
@ -52,156 +53,150 @@ def test_abandon_purged_tx(
|
|||
bitcoind_controller = instantiate_bitcoind_controller(
|
||||
docker,
|
||||
request,
|
||||
rpcport=18968,
|
||||
rpcport=18998,
|
||||
extra_args=["-acceptnonstdtxn=1", "-maxmempool=5", "-spendzeroconfchange=0"],
|
||||
)
|
||||
rpcconn = bitcoind_controller.rpcconn
|
||||
rpc = rpcconn.get_rpc()
|
||||
assert rpc is not None
|
||||
assert rpc.ipaddress != None
|
||||
|
||||
# Note: Our utxo creation is simpler than mempool_limit.py's approach since we're
|
||||
# running in regtest and can just use generatetoaddress().
|
||||
|
||||
# Instantiate a new Specter instance to talk to this bitcoind
|
||||
config = {
|
||||
"rpc": {
|
||||
"autodetect": False,
|
||||
"datadir": "",
|
||||
"user": rpcconn.rpcuser,
|
||||
"password": rpcconn.rpcpassword,
|
||||
"port": rpcconn.rpcport,
|
||||
"host": rpcconn.ipaddress,
|
||||
"protocol": "http",
|
||||
},
|
||||
"auth": {
|
||||
"method": "rpcpasswordaspin",
|
||||
},
|
||||
}
|
||||
specter = Specter(data_folder=devices_filled_data_folder, config=config)
|
||||
specter.check()
|
||||
|
||||
assert specter.info["mempool_info"]["maxmempool"] == 5 * 1000 * 1000 # 5MB
|
||||
|
||||
# Largely copy-and-paste from test_wallet_manager.test_wallet_createpsbt.
|
||||
# TODO: Make a test fixture in conftest.py that sets up already funded wallets
|
||||
# for a bitcoin core hot wallet.
|
||||
wallet_manager = WalletManager(
|
||||
200100,
|
||||
devices_filled_data_folder,
|
||||
rpc,
|
||||
"regtest",
|
||||
device_manager,
|
||||
allow_threading=False,
|
||||
)
|
||||
|
||||
# Create a new device that can sign psbts (Bitcoin Core hot wallet)
|
||||
device = device_manager.add_device(
|
||||
name="bitcoin_core_hot_wallet", device_type="bitcoincore", keys=[]
|
||||
)
|
||||
device.setup_device(file_password=None, wallet_manager=wallet_manager)
|
||||
device.add_hot_wallet_keys(
|
||||
mnemonic=generate_mnemonic(strength=128),
|
||||
passphrase="",
|
||||
paths=["m/49h/0h/0h"],
|
||||
file_password=None,
|
||||
wallet_manager=wallet_manager,
|
||||
testnet=True,
|
||||
keys_range=[0, 1000],
|
||||
keys_purposes=[],
|
||||
)
|
||||
|
||||
wallet = wallet_manager.create_wallet(
|
||||
"bitcoincore_test_wallet", 1, "sh-wpkh", [device.keys[0]], [device]
|
||||
)
|
||||
|
||||
# Fund the wallet. Going to need a LOT of utxos to play with.
|
||||
logging.info("Generating utxos to wallet")
|
||||
address = wallet.getnewaddress()
|
||||
wallet.rpc.generatetoaddress(91, address)
|
||||
|
||||
# newly minted coins need 100 blocks to get spendable
|
||||
# let's mine another 100 blocks to get these coins spendable
|
||||
wallet.rpc.generatetoaddress(101, address)
|
||||
|
||||
# update the wallet data
|
||||
wallet.get_balance()
|
||||
|
||||
# ==== Begin test from mempool_limit.py ====
|
||||
txouts = gen_return_txouts()
|
||||
relayfee = satoshi_round(rpc.getnetworkinfo()["relayfee"])
|
||||
|
||||
logging.info("Check that mempoolminfee is minrelytxfee")
|
||||
assert satoshi_round(rpc.getmempoolinfo()["minrelaytxfee"]) == Decimal("0.00001000")
|
||||
assert satoshi_round(rpc.getmempoolinfo()["mempoolminfee"]) == Decimal("0.00001000")
|
||||
|
||||
txids = []
|
||||
utxos = wallet.rpc.listunspent()
|
||||
|
||||
logging.info("Create a mempool tx that will be evicted")
|
||||
us0 = utxos.pop()
|
||||
inputs = [{"txid": us0["txid"], "vout": us0["vout"]}]
|
||||
outputs = {wallet.getnewaddress(): 0.0001}
|
||||
tx = wallet.rpc.createrawtransaction(inputs, outputs)
|
||||
wallet.rpc.settxfee(str(relayfee)) # specifically fund this tx with low fee
|
||||
txF = wallet.rpc.fundrawtransaction(tx)
|
||||
wallet.rpc.settxfee(0) # return to automatic fee selection
|
||||
txFS = device.sign_raw_tx(txF["hex"], wallet)
|
||||
txid = wallet.rpc.sendrawtransaction(txFS["hex"])
|
||||
|
||||
# ==== Specter-specific: can't abandon a valid pending tx ====
|
||||
try:
|
||||
assert bitcoind_controller.get_rpc().test_connection()
|
||||
rpcconn = bitcoind_controller.rpcconn
|
||||
rpc = rpcconn.get_rpc()
|
||||
assert rpc is not None
|
||||
assert rpc.ipaddress != None
|
||||
|
||||
# Note: Our utxo creation is simpler than mempool_limit.py's approach since we're
|
||||
# running in regtest and can just use generatetoaddress().
|
||||
|
||||
# Instantiate a new Specter instance to talk to this bitcoind
|
||||
config = {
|
||||
"rpc": {
|
||||
"autodetect": False,
|
||||
"datadir": "",
|
||||
"user": rpcconn.rpcuser,
|
||||
"password": rpcconn.rpcpassword,
|
||||
"port": rpcconn.rpcport,
|
||||
"host": rpcconn.ipaddress,
|
||||
"protocol": "http",
|
||||
},
|
||||
"auth": {
|
||||
"method": "rpcpasswordaspin",
|
||||
},
|
||||
}
|
||||
specter = Specter(data_folder=devices_filled_data_folder, config=config)
|
||||
specter.check()
|
||||
|
||||
assert specter.info["mempool_info"]["maxmempool"] == 5 * 1000 * 1000 # 5MB
|
||||
|
||||
# Largely copy-and-paste from test_wallet_manager.test_wallet_createpsbt.
|
||||
# TODO: Make a test fixture in conftest.py that sets up already funded wallets
|
||||
# for a bitcoin core hot wallet.
|
||||
wallet_manager = WalletManager(
|
||||
200100,
|
||||
devices_filled_data_folder,
|
||||
rpc,
|
||||
"regtest",
|
||||
device_manager,
|
||||
allow_threading=False,
|
||||
)
|
||||
|
||||
# Create a new device that can sign psbts (Bitcoin Core hot wallet)
|
||||
device = device_manager.add_device(
|
||||
name="bitcoin_core_hot_wallet", device_type="bitcoincore", keys=[]
|
||||
)
|
||||
device.setup_device(file_password=None, wallet_manager=wallet_manager)
|
||||
device.add_hot_wallet_keys(
|
||||
mnemonic=generate_mnemonic(strength=128),
|
||||
passphrase="",
|
||||
paths=["m/49h/0h/0h"],
|
||||
file_password=None,
|
||||
wallet_manager=wallet_manager,
|
||||
testnet=True,
|
||||
keys_range=[0, 1000],
|
||||
keys_purposes=[],
|
||||
)
|
||||
|
||||
wallet = wallet_manager.create_wallet(
|
||||
"bitcoincore_test_wallet", 1, "sh-wpkh", [device.keys[0]], [device]
|
||||
)
|
||||
|
||||
# Fund the wallet. Going to need a LOT of utxos to play with.
|
||||
logging.info("Generating utxos to wallet")
|
||||
address = wallet.getnewaddress()
|
||||
wallet.rpc.generatetoaddress(91, address)
|
||||
|
||||
# newly minted coins need 100 blocks to get spendable
|
||||
# let's mine another 100 blocks to get these coins spendable
|
||||
wallet.rpc.generatetoaddress(101, address)
|
||||
|
||||
# update the wallet data
|
||||
wallet.get_balance()
|
||||
|
||||
# ==== Begin test from mempool_limit.py ====
|
||||
txouts = gen_return_txouts()
|
||||
relayfee = satoshi_round(rpc.getnetworkinfo()["relayfee"])
|
||||
|
||||
logging.info("Check that mempoolminfee is minrelytxfee")
|
||||
assert satoshi_round(rpc.getmempoolinfo()["minrelaytxfee"]) == Decimal(
|
||||
"0.00001000"
|
||||
)
|
||||
assert satoshi_round(rpc.getmempoolinfo()["mempoolminfee"]) == Decimal(
|
||||
"0.00001000"
|
||||
)
|
||||
|
||||
txids = []
|
||||
utxos = wallet.rpc.listunspent()
|
||||
|
||||
logging.info("Create a mempool tx that will be evicted")
|
||||
us0 = utxos.pop()
|
||||
inputs = [{"txid": us0["txid"], "vout": us0["vout"]}]
|
||||
outputs = {wallet.getnewaddress(): 0.0001}
|
||||
tx = wallet.rpc.createrawtransaction(inputs, outputs)
|
||||
wallet.rpc.settxfee(str(relayfee)) # specifically fund this tx with low fee
|
||||
txF = wallet.rpc.fundrawtransaction(tx)
|
||||
wallet.rpc.settxfee(0) # return to automatic fee selection
|
||||
txFS = device.sign_raw_tx(txF["hex"], wallet)
|
||||
txid = wallet.rpc.sendrawtransaction(txFS["hex"])
|
||||
|
||||
# ==== Specter-specific: can't abandon a valid pending tx ====
|
||||
try:
|
||||
wallet.abandontransaction(txid)
|
||||
except SpecterError as e:
|
||||
assert "Cannot abandon" in str(e)
|
||||
|
||||
# ==== Resume test from mempool_limit.py ====
|
||||
# Spam the mempool with big transactions!
|
||||
relayfee = satoshi_round(rpc.getnetworkinfo()["relayfee"])
|
||||
base_fee = float(relayfee) * 100
|
||||
for i in range(3):
|
||||
txids.append([])
|
||||
txids[i] = create_lots_of_big_transactions(
|
||||
wallet, txouts, utxos[30 * i : 30 * i + 30], 30, (i + 1) * base_fee
|
||||
)
|
||||
|
||||
logging.info("The tx should be evicted by now")
|
||||
assert txid not in wallet.rpc.getrawmempool()
|
||||
txdata = wallet.rpc.gettransaction(txid)
|
||||
assert txdata["confirmations"] == 0 # confirmation should still be 0
|
||||
|
||||
# ==== Specter-specific: Verify purge and abandon ====
|
||||
assert wallet.is_tx_purged(txid)
|
||||
wallet.abandontransaction(txid)
|
||||
except SpecterError as e:
|
||||
assert "Cannot abandon" in str(e)
|
||||
|
||||
# tx will still be in the wallet but marked "abandoned"
|
||||
txdata = wallet.rpc.gettransaction(txid)
|
||||
for detail in txdata["details"]:
|
||||
if detail["category"] == "send":
|
||||
assert detail["abandoned"]
|
||||
# ==== Resume test from mempool_limit.py ====
|
||||
# Spam the mempool with big transactions!
|
||||
relayfee = satoshi_round(rpc.getnetworkinfo()["relayfee"])
|
||||
base_fee = float(relayfee) * 100
|
||||
for i in range(3):
|
||||
txids.append([])
|
||||
txids[i] = create_lots_of_big_transactions(
|
||||
wallet, txouts, utxos[30 * i : 30 * i + 30], 30, (i + 1) * base_fee
|
||||
)
|
||||
|
||||
# Can we now spend those same inputs?
|
||||
outputs = {wallet.getnewaddress(): 0.0001}
|
||||
tx = wallet.rpc.createrawtransaction(inputs, outputs)
|
||||
logging.info("The tx should be evicted by now")
|
||||
assert txid not in wallet.rpc.getrawmempool()
|
||||
txdata = wallet.rpc.gettransaction(txid)
|
||||
assert txdata["confirmations"] == 0 # confirmation should still be 0
|
||||
|
||||
# Fund this tx with a high enough fee
|
||||
relayfee = satoshi_round(rpc.getnetworkinfo()["relayfee"])
|
||||
wallet.rpc.settxfee(str(relayfee * Decimal("3.0")))
|
||||
# ==== Specter-specific: Verify purge and abandon ====
|
||||
assert wallet.is_tx_purged(txid)
|
||||
wallet.abandontransaction(txid)
|
||||
|
||||
txF = wallet.rpc.fundrawtransaction(tx)
|
||||
wallet.rpc.settxfee(0) # return to automatic fee selection
|
||||
txFS = device.sign_raw_tx(txF["hex"], wallet)
|
||||
txid = wallet.rpc.sendrawtransaction(txFS["hex"])
|
||||
# tx will still be in the wallet but marked "abandoned"
|
||||
txdata = wallet.rpc.gettransaction(txid)
|
||||
for detail in txdata["details"]:
|
||||
if detail["category"] == "send":
|
||||
assert detail["abandoned"]
|
||||
|
||||
# Should have been accepted by the mempool
|
||||
assert txid in wallet.rpc.getrawmempool()
|
||||
assert wallet.get_balance()["untrusted_pending"] == 0.0001
|
||||
finally:
|
||||
# Clean up
|
||||
bitcoind_controller.stop_bitcoind()
|
||||
# Can we now spend those same inputs?
|
||||
outputs = {wallet.getnewaddress(): 0.0001}
|
||||
tx = wallet.rpc.createrawtransaction(inputs, outputs)
|
||||
|
||||
# Fund this tx with a high enough fee
|
||||
relayfee = satoshi_round(rpc.getnetworkinfo()["relayfee"])
|
||||
wallet.rpc.settxfee(str(relayfee * Decimal("3.0")))
|
||||
|
||||
txF = wallet.rpc.fundrawtransaction(tx)
|
||||
wallet.rpc.settxfee(0) # return to automatic fee selection
|
||||
txFS = device.sign_raw_tx(txF["hex"], wallet)
|
||||
txid = wallet.rpc.sendrawtransaction(txFS["hex"])
|
||||
|
||||
# Should have been accepted by the mempool
|
||||
assert txid in wallet.rpc.getrawmempool()
|
||||
assert wallet.get_balance()["untrusted_pending"] == 0.0001
|
||||
|
||||
# Clean up
|
||||
bitcoind_controller.stop_bitcoind()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue