Skip to content

Commit a5bd911

Browse files
Merge pull request #83 from HathorNetwork/dev
Release v0.8.0
2 parents b1401c2 + af2eda9 commit a5bd911

File tree

9 files changed

+443
-9
lines changed

9 files changed

+443
-9
lines changed

docs/apidocs.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ servers:
44
info:
55
title: tx-mining-service API
66
description: This service is used to resolving transactions before propagating them into the network
7-
version: 0.7.0
7+
version: 0.8.0
88
paths:
99
/health-check:
1010
get:

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
[tool.poetry]
77
name = "tx-mining-service"
8-
version = "0.7.0"
8+
version = "0.8.0"
99
description = "Service to mine transactions"
1010
authors = ["Hathor Team <contact@hathor.network>"]
1111
license = "MIT"

tests/test_api.py

Lines changed: 198 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
App,
1717
)
1818
from txstratum.manager import TxMiningManager
19+
from txstratum.middleware import VERSION_CHECK_ERROR_MESSAGE
1920
from txstratum.utils import tx_or_block_from_bytes
2021

2122
from .tx_examples import INVALID_TX_DATA
@@ -123,11 +124,9 @@ def get_timestamp(tx_bytes: bytes) -> int:
123124
return tx.timestamp
124125

125126

126-
class AppTestCase(AioHTTPTestCase):
127-
async def get_application(self):
128-
self.manager = TxMiningManager(backend=None, pubsub=MagicMock(), address=None)
129-
self.myapp = App(self.manager)
130-
return self.myapp.app
127+
class BaseAppTestCase(AioHTTPTestCase):
128+
__test__ = False
129+
version_check: bool
131130

132131
@unittest_run_loop
133132
async def test_health_check(self):
@@ -486,3 +485,197 @@ async def test_submit_job_fail_script_data26_non_standard(self):
486485
data = await resp.json()
487486
self.assertEqual(400, resp.status)
488487
self.assertEqual({"error": "non-standard-tx"}, data)
488+
489+
@unittest_run_loop
490+
async def test_check_wallet_version(self):
491+
tx_hex = update_timestamp(TX_SCRIPT_DATA25).hex()
492+
493+
header_desktop_lower = {
494+
"User-Agent": (
495+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)"
496+
"HathorWallet/0.22.1 Chrome/73.0.3683.121 Electron/5.0.13 Safari/537.36 HathorWallet/0.22.1"
497+
)
498+
}
499+
header_desktop_equal = {
500+
"User-Agent": (
501+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)"
502+
"HathorWallet/0.23.0 Chrome/73.0.3683.121 Electron/5.0.13 Safari/537.36 HathorWallet/0.23.0"
503+
)
504+
}
505+
header_desktop_higher = {
506+
"User-Agent": (
507+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)"
508+
"HathorWallet/0.23.1 Chrome/73.0.3683.121 Electron/5.0.13 Safari/537.36 HathorWallet/0.23.1"
509+
)
510+
}
511+
512+
# Success with desktop version higher than the minimum
513+
resp = await self.client.request(
514+
"POST", "/submit-job", json={"tx": tx_hex}, headers=header_desktop_higher
515+
)
516+
await resp.json()
517+
self.assertEqual(200, resp.status)
518+
519+
# Success with desktop version equal to the minimum
520+
resp = await self.client.request(
521+
"POST", "/submit-job", json={"tx": tx_hex}, headers=header_desktop_equal
522+
)
523+
await resp.json()
524+
self.assertEqual(200, resp.status)
525+
526+
# Error with desktop version lower than the minimum if the app is validating desktop version
527+
resp = await self.client.request(
528+
"POST", "/submit-job", json={"tx": tx_hex}, headers=header_desktop_lower
529+
)
530+
data = await resp.json()
531+
if self.version_check:
532+
self.assertEqual(400, resp.status)
533+
self.assertEqual(
534+
{
535+
"error": VERSION_CHECK_ERROR_MESSAGE,
536+
"data": {"min_version": "0.23.0", "version": "0.22.1"},
537+
},
538+
data,
539+
)
540+
else:
541+
self.assertEqual(200, resp.status)
542+
543+
header_mobile_lower = {"User-Agent": "Hathor Wallet Mobile / 0.18.0"}
544+
header_mobile_equal = {"User-Agent": "Hathor Wallet Mobile / 1.18.3"}
545+
header_mobile_higher = {"User-Agent": "Hathor Wallet Mobile / 20.1.0"}
546+
header_mobile_custom = {"User-Agent": "HathorMobile/1"}
547+
header_mobile_custom_wrong = {"User-Agent": "HathorMobile/2"}
548+
549+
# Success with mobile version higher than the minimum
550+
resp = await self.client.request(
551+
"POST", "/submit-job", json={"tx": tx_hex}, headers=header_mobile_higher
552+
)
553+
await resp.json()
554+
self.assertEqual(200, resp.status)
555+
556+
# Success with mobile version equal to the minimum
557+
resp = await self.client.request(
558+
"POST", "/submit-job", json={"tx": tx_hex}, headers=header_mobile_equal
559+
)
560+
await resp.json()
561+
self.assertEqual(200, resp.status)
562+
563+
# Error with mobile version lower than the minimum if the app is validating mobile version
564+
resp = await self.client.request(
565+
"POST", "/submit-job", json={"tx": tx_hex}, headers=header_mobile_lower
566+
)
567+
data = await resp.json()
568+
if self.version_check:
569+
self.assertEqual(400, resp.status)
570+
self.assertEqual(
571+
{
572+
"error": VERSION_CHECK_ERROR_MESSAGE,
573+
"data": {"min_version": "1.18.3", "version": "0.18.0"},
574+
},
575+
data,
576+
)
577+
else:
578+
self.assertEqual(200, resp.status)
579+
580+
# Error with mobile version before v0.18.0, if the app is validating mobile version
581+
resp = await self.client.request(
582+
"POST", "/submit-job", json={"tx": tx_hex}, headers=header_mobile_custom
583+
)
584+
data = await resp.json()
585+
if self.version_check:
586+
self.assertEqual(400, resp.status)
587+
self.assertEqual(
588+
{
589+
"error": VERSION_CHECK_ERROR_MESSAGE,
590+
"data": {"min_version": "1.18.3"},
591+
},
592+
data,
593+
)
594+
else:
595+
self.assertEqual(200, resp.status)
596+
597+
# Success if the custom is different than expected
598+
resp = await self.client.request(
599+
"POST",
600+
"/submit-job",
601+
json={"tx": tx_hex},
602+
headers=header_mobile_custom_wrong,
603+
)
604+
await resp.json()
605+
self.assertEqual(200, resp.status)
606+
607+
header_headless_lower = {"User-Agent": "Hathor Wallet Headless / 0.14.87"}
608+
header_headless_equal = {"User-Agent": "Hathor Wallet Headless / 0.14.88"}
609+
header_headless_higher = {"User-Agent": "Hathor Wallet Headless / 0.14.89"}
610+
611+
# Success with headless version higher than the minimum
612+
resp = await self.client.request(
613+
"POST", "/submit-job", json={"tx": tx_hex}, headers=header_headless_higher
614+
)
615+
await resp.json()
616+
self.assertEqual(200, resp.status)
617+
618+
# Success with headless version equal to the minimum
619+
resp = await self.client.request(
620+
"POST", "/submit-job", json={"tx": tx_hex}, headers=header_headless_equal
621+
)
622+
await resp.json()
623+
self.assertEqual(200, resp.status)
624+
625+
# Error with headless version lower than the minimum if the app is validating headless version
626+
resp = await self.client.request(
627+
"POST", "/submit-job", json={"tx": tx_hex}, headers=header_headless_lower
628+
)
629+
data = await resp.json()
630+
if self.version_check:
631+
self.assertEqual(400, resp.status)
632+
self.assertEqual(
633+
{
634+
"error": VERSION_CHECK_ERROR_MESSAGE,
635+
"data": {"min_version": "0.14.88", "version": "0.14.87"},
636+
},
637+
data,
638+
)
639+
else:
640+
self.assertEqual(200, resp.status)
641+
642+
header_headless_wrong1 = {"User-Agent": "Hathor Wallet Headless / 0.xx.88"}
643+
header_headless_wrong2 = {"User-Agent": "Hathor Wallet Headless / 0.14.88beta"}
644+
645+
# Success in both cases because the regex shouldn't identify this as headless versions
646+
resp = await self.client.request(
647+
"POST", "/submit-job", json={"tx": tx_hex}, headers=header_headless_wrong1
648+
)
649+
await resp.json()
650+
self.assertEqual(200, resp.status)
651+
652+
resp = await self.client.request(
653+
"POST", "/submit-job", json={"tx": tx_hex}, headers=header_headless_wrong2
654+
)
655+
await resp.json()
656+
self.assertEqual(200, resp.status)
657+
658+
659+
class AppTestCase(BaseAppTestCase):
660+
__test__ = True
661+
662+
async def get_application(self):
663+
self.manager = TxMiningManager(backend=None, pubsub=MagicMock(), address=None)
664+
self.myapp = App(self.manager)
665+
self.version_check = False
666+
return self.myapp.app
667+
668+
669+
class AppVersionCheckTestCase(BaseAppTestCase):
670+
__test__ = True
671+
672+
async def get_application(self):
673+
self.manager = TxMiningManager(backend=None, pubsub=MagicMock(), address=None)
674+
self.myapp = App(
675+
self.manager,
676+
min_wallet_desktop_version="0.23.0",
677+
min_wallet_mobile_version="1.18.3",
678+
min_wallet_headless_version="0.14.88",
679+
)
680+
self.version_check = True
681+
return self.myapp.app

tests/test_utils.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
"""
2+
Copyright (c) Hathor Labs and its affiliates.
3+
4+
This source code is licensed under the MIT license found in the
5+
LICENSE file in the root directory of this source tree.
6+
"""
7+
import unittest
8+
9+
from txstratum.exceptions import InvalidVersionFormat
10+
from txstratum.utils import is_version_gte
11+
12+
13+
class UtilsTestCase(unittest.TestCase):
14+
def test_versions_check(self):
15+
self.assertTrue(is_version_gte("1.2.3", "1.2.3"))
16+
self.assertFalse(is_version_gte("1.2.2", "1.2.3"))
17+
self.assertTrue(is_version_gte("1.2.4", "1.2.3"))
18+
19+
self.assertTrue(is_version_gte("2.2.4", "1.2.3"))
20+
self.assertTrue(is_version_gte("1.3.4", "1.2.3"))
21+
self.assertFalse(is_version_gte("0.3.4", "1.2.3"))
22+
23+
self.assertFalse(is_version_gte("0.3.4", "1.2.3"))
24+
25+
with self.assertRaises(InvalidVersionFormat):
26+
is_version_gte("0.3.4.5", "0.3.4")
27+
28+
with self.assertRaises(InvalidVersionFormat):
29+
is_version_gte("0.3.4", "0.3.4.5")

txstratum/api.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import txstratum.time
1515
from txstratum.exceptions import JobAlreadyExists, NewJobRefused
1616
from txstratum.jobs import JobStatus, TxJob
17+
from txstratum.middleware import create_middleware_version_check
1718
from txstratum.utils import tx_or_block_from_bytes
1819

1920
if TYPE_CHECKING:
@@ -50,7 +51,10 @@ def __init__(
5051
fix_invalid_timestamp: bool = False,
5152
max_output_script_size: Optional[int] = None,
5253
only_standard_script: bool = True,
53-
tx_filters: Optional[List["TXFilter"]] = None
54+
tx_filters: Optional[List["TXFilter"]] = None,
55+
min_wallet_desktop_version: Optional[str] = None,
56+
min_wallet_mobile_version: Optional[str] = None,
57+
min_wallet_headless_version: Optional[str] = None,
5458
):
5559
"""Init App."""
5660
super().__init__()
@@ -62,7 +66,15 @@ def __init__(
6266
self.tx_timeout: float = tx_timeout or TX_TIMEOUT
6367
self.only_standard_script: bool = only_standard_script
6468
self.tx_filters = tx_filters or []
65-
self.app = web.Application()
69+
self.app = web.Application(
70+
middlewares=[
71+
create_middleware_version_check(
72+
min_wallet_desktop_version,
73+
min_wallet_mobile_version,
74+
min_wallet_headless_version,
75+
)
76+
]
77+
)
6678
self.app.router.add_get("/health-check", self.health_check)
6779
self.app.router.add_get("/mining-status", self.mining_status)
6880
self.app.router.add_get("/job-status", self.job_status)

txstratum/cli.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,27 @@ def create_parser() -> ArgumentParser:
109109
"backend", help="Endpoint of the Hathor API (without version)", type=str
110110
)
111111

112+
parser.add_argument(
113+
"--min-wallet-desktop-version",
114+
help="Minimum version for the wallet desktop to use this tx mining",
115+
type=str,
116+
default=None,
117+
)
118+
119+
parser.add_argument(
120+
"--min-wallet-mobile-version",
121+
help="Minimum version for the wallet mobile to use this tx mining",
122+
type=str,
123+
default=None,
124+
)
125+
126+
parser.add_argument(
127+
"--min-wallet-headless-version",
128+
help="Minimum version for the wallet headless to use this tx mining",
129+
type=str,
130+
default=None,
131+
)
132+
112133
logs = parser.add_mutually_exclusive_group()
113134
logs.add_argument(
114135
"--log-config",
@@ -238,6 +259,9 @@ def execute(self) -> None:
238259
fix_invalid_timestamp=self.args.fix_invalid_timestamp,
239260
only_standard_script=not self.args.allow_non_standard_script,
240261
tx_filters=self.tx_filters,
262+
min_wallet_desktop_version=self.args.min_wallet_desktop_version,
263+
min_wallet_mobile_version=self.args.min_wallet_mobile_version,
264+
min_wallet_headless_version=self.args.min_wallet_headless_version,
241265
)
242266

243267
logger.info(
@@ -248,6 +272,9 @@ def execute(self) -> None:
248272
fix_invalid_timestamp=api_app.fix_invalid_timestamp,
249273
only_standard_script=api_app.only_standard_script,
250274
tx_filters=self.tx_filters,
275+
min_wallet_desktop_version=self.args.min_wallet_desktop_version,
276+
min_wallet_mobile_version=self.args.min_wallet_mobile_version,
277+
min_wallet_headless_version=self.args.min_wallet_headless_version,
251278
)
252279

253280
web_runner = web.AppRunner(api_app.app, logger=logger)

txstratum/exceptions.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,9 @@ class JobAlreadyExists(Exception):
88
"""Exception raised when a job already exists."""
99

1010
pass
11+
12+
13+
class InvalidVersionFormat(Exception):
14+
"""Exception raised when comparing versions that are in the wrong format."""
15+
16+
pass

0 commit comments

Comments
 (0)