From 6c345f46f54210792c1285b5f5039d3cb464f933 Mon Sep 17 00:00:00 2001 From: cyyber Date: Tue, 1 Nov 2022 14:18:10 +0530 Subject: [PATCH 01/15] Updated dependencies version --- requirements.txt | 8 ++++---- setup.cfg | 14 +++++++------- test-requirements.txt | 6 +++--- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/requirements.txt b/requirements.txt index 703d1f25d..4be351ae9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,18 +1,18 @@ # Fixing sphinx version due to https://github.com/sphinx-doc/sphinx/issues/3976 # setuptools==50.3.2 # It needs to be installed manually in some cases -plyvel==1.2.0 +plyvel==1.4.0 ntplib==0.3.4 Twisted==20.3.0 colorlog==3.1.0 simplejson==3.11.1 PyYAML==5.3.1 -grpcio-tools>=1.9.0,<=1.27.2 -grpcio>=1.9.0,<=1.27.2 +grpcio-tools>=1.9.0,<=1.50.0 +grpcio>=1.9.0,<=1.50.0 google-api-python-client==1.8.3 google-auth<2.0dev,>=1.21.1 httplib2>=0.15.0 service_identity==17.0.0 -protobuf==3.15.8 +protobuf==3.20.3 pyopenssl==17.5.0 six==1.13.0 click==7.1.2 diff --git a/setup.cfg b/setup.cfg index 2910f5df0..7b00ebe1f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -26,19 +26,19 @@ package_dir = # install_requires = numpy; scipy install_requires = # setuptools==50.3.2 - plyvel==1.2.0 + plyvel==1.4.0 ntplib==0.3.4 Twisted==20.3.0 colorlog==3.1.0 simplejson==3.11.1 PyYAML==5.3.1 - grpcio-tools>=1.9.0,<=1.27.2 - grpcio>=1.9.0,<=1.27.2 + grpcio-tools>=1.9.0,<=1.50.0 + grpcio>=1.9.0,<=1.50.0 google-api-python-client==1.8.3 google-auth<2.0dev,>=1.21.1 httplib2>=0.15.0 service_identity==17.0.0 - protobuf==3.15.8 + protobuf==3.20.3 pyopenssl==17.5.0 six==1.13.0 click==7.1.2 @@ -54,9 +54,9 @@ install_requires = # Add here test requirements (semicolon-separated) tests_require = - pytest==3.6 - pytest-cov==2.5.1 - pytest-xdist==1.22.2 + pytest==7.1.3 + pytest-cov==4.0.0 + pytest-xdist==2.5.0 pytest-flake8==1.0.0 flake8==3.5.0 autoflake==1.1 diff --git a/test-requirements.txt b/test-requirements.txt index ec6984a5d..039dc5e1e 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,9 +1,9 @@ # Add requirements only needed for your unittests and during development here. # They will be installed automatically when running `python setup.py test`. # ATTENTION: Don't remove pytest-cov and pytest as they are needed. -pytest==3.6 -pytest-cov==2.5.1 -pytest-xdist==1.22.2 +pytest==7.1.3 +pytest-cov==4.0.0 +pytest-xdist==2.5.0 flake8==3.5.0 autoflake==1.1 timeout-decorator==0.4.0 From 43367891b840c099bdd7171b6fa64c17ab8447f0 Mon Sep 17 00:00:00 2001 From: cyyber Date: Tue, 1 Nov 2022 14:18:36 +0530 Subject: [PATCH 02/15] Added code to get seed_block for alt chain --- src/qrl/core/ChainManager.py | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/qrl/core/ChainManager.py b/src/qrl/core/ChainManager.py index dc102ac41..fd3f4229f 100644 --- a/src/qrl/core/ChainManager.py +++ b/src/qrl/core/ChainManager.py @@ -327,6 +327,24 @@ def get_config_by_block_number(self, block_number: int) -> config.DevConfig: dev_config = config.DevConfig(dev_config_pb_data, True, True) return dev_config + def get_seed_block(self, blockheader: BlockHeader): + qn = Qryptonight() + seed_height = qn.get_seed_height(blockheader.block_number) + + # If parent block belongs to main chain, then seed block will also be in the main chain + prev_mainchain_block = self.get_block_by_number(blockheader.block_number - 1) + if prev_mainchain_block.headerhash == blockheader.prev_headerhash: + return self.get_block_by_number(seed_height) + + prev_block = self.get_block(blockheader.prev_headerhash) + while prev_block.block_number > seed_height: + prev_mainchain_block = self.get_block_by_number(prev_block.block_number) + if prev_mainchain_block.headerhash == prev_block.headerhash: + return self.get_block_by_number(seed_height) + prev_block = self.get_block(prev_block.prev_headerhash) + + return prev_block + def validate_mining_nonce(self, blockheader: BlockHeader, dev_config: config.DevConfig, enable_logging=True): with self.lock: parent_metadata = BlockMetadata.get_block_metadata(self._state, blockheader.prev_headerhash) @@ -342,6 +360,8 @@ def validate_mining_nonce(self, blockheader: BlockHeader, dev_config: config.Dev dev_config=dev_config) mining_blob = blockheader.mining_blob(dev_config) + qn = Qryptonight() + seed_block = self.get_seed_block(blockheader) if enable_logging: logger.debug('-----------------START--------------------') @@ -352,10 +372,10 @@ def validate_mining_nonce(self, blockheader: BlockHeader, dev_config: config.Dev logger.debug('diff %s', UInt256ToString(diff)) logger.debug('target %s', bin2hstr(target)) logger.debug('mining blob %s', bin2hstr(mining_blob)) + logger.debug('seed_block #%s', seed_block.block_number) + logger.debug('seed_block hash %s', bin2hstr(seed_block.headerhash)) logger.debug('-------------------END--------------------') - qn = Qryptonight() - seed_block = self.get_block_by_number(qn.get_seed_height(blockheader.block_number)) if not PoWValidator().verify_input(blockheader.block_number, seed_block.block_number, seed_block.headerhash, From 14e6dbbac639ef7172bd3b16ebca8ff9435a9bcf Mon Sep 17 00:00:00 2001 From: cyyber Date: Tue, 1 Nov 2022 14:19:22 +0530 Subject: [PATCH 03/15] import Set from collections.abc for compatibility with Python 3.10+ --- src/qrl/core/misc/expiring_set.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qrl/core/misc/expiring_set.py b/src/qrl/core/misc/expiring_set.py index 86cc608ca..66fe4e81a 100644 --- a/src/qrl/core/misc/expiring_set.py +++ b/src/qrl/core/misc/expiring_set.py @@ -1,7 +1,7 @@ # coding=utf-8 # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. -from collections import Set +from collections.abc import Set import simplejson as json From cc9442af55d978f9f83ec07e244e54fe4fdd4df4 Mon Sep 17 00:00:00 2001 From: cyyber Date: Wed, 16 Nov 2022 19:38:31 +0530 Subject: [PATCH 04/15] Updated dependencies version --- requirements.txt | 10 +++++----- setup.cfg | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/requirements.txt b/requirements.txt index 4be351ae9..44cac0178 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ # Fixing sphinx version due to https://github.com/sphinx-doc/sphinx/issues/3976 # setuptools==50.3.2 # It needs to be installed manually in some cases -plyvel==1.4.0 +plyvel>=1.2.0,<=1.4.0 ntplib==0.3.4 Twisted==20.3.0 colorlog==3.1.0 @@ -12,15 +12,15 @@ google-api-python-client==1.8.3 google-auth<2.0dev,>=1.21.1 httplib2>=0.15.0 service_identity==17.0.0 -protobuf==3.20.3 +protobuf>3.19.0,<=3.20.3 pyopenssl==17.5.0 six==1.13.0 -click==7.1.2 +click==8.0 pyqrllib>=1.2.3,<1.3.0 pyqryptonight>=0.99.9 pyqrandomx>=0.3.0,<1.0.0 -Flask>=1.0.0,<=1.1.2 -json-rpc==1.10.8 +Flask>=2.0.0,<=2.2.2 +json-rpc==1.13.0 idna==2.6 cryptography==2.3 mock==2.0.0 diff --git a/setup.cfg b/setup.cfg index 7b00ebe1f..39a8301c9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -26,7 +26,7 @@ package_dir = # install_requires = numpy; scipy install_requires = # setuptools==50.3.2 - plyvel==1.4.0 + plyvel>=1.2.0,<=1.4.0 ntplib==0.3.4 Twisted==20.3.0 colorlog==3.1.0 @@ -38,15 +38,15 @@ install_requires = google-auth<2.0dev,>=1.21.1 httplib2>=0.15.0 service_identity==17.0.0 - protobuf==3.20.3 + protobuf>3.19.0,<=3.20.3 pyopenssl==17.5.0 six==1.13.0 - click==7.1.2 + click==8.0 pyqrllib>=1.2.3,<1.3.0 pyqryptonight>=0.99.9 pyqrandomx>=0.3.0,<1.0.0 - Flask>=1.0.0,<=1.1.2 - json-rpc==1.10.8 + Flask>=2.0.0,<=2.2.2 + json-rpc==1.13.0 idna==2.6 cryptography==2.3 mock==2.0.0 From 2b3a14560697dfb65ed279152b8c89f3636cccbb Mon Sep 17 00:00:00 2001 From: cyyber Date: Wed, 16 Nov 2022 20:07:07 +0530 Subject: [PATCH 05/15] Removed support for Ubuntu trusty and xenial --- .circleci/config.yml | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 99e676efc..6eb7edb4f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,7 +2,7 @@ version: 2 jobs: build: docker: - - image: qrledger/qrl-docker-ci:xenial + - image: qrledger/qrl-docker-ci:bionic steps: - checkout - run: pip install -U setuptools @@ -14,7 +14,7 @@ jobs: test_leaks_CLI: docker: - - image: qrledger/qrl-docker-ci:xenial + - image: qrledger/qrl-docker-ci:bionic steps: - checkout - run: pip install -U setuptools @@ -25,7 +25,7 @@ jobs: flake8: docker: - - image: qrledger/qrl-docker-ci:xenial + - image: qrledger/qrl-docker-ci:bionic steps: - checkout - run: pip install -U setuptools @@ -33,7 +33,7 @@ jobs: test_leaks_other: docker: - - image: qrledger/qrl-docker-ci:xenial + - image: qrledger/qrl-docker-ci:bionic steps: - checkout - run: pip install -U setuptools @@ -42,17 +42,6 @@ jobs: - run: pip install -U pytest-openfiles pytest-leaks pytest-threadleak pytest-repeat - run: python3 setup.py test --addopts "--ignore=tests/tools/ --ignore=tests/daemon/ --ignore=tests/services/test_WalletAPIService.py --open-files --threadleak -n0" - build_trusty: - docker: - - image: qrledger/qrl-docker-ci:trusty - steps: - - checkout - - run: pip install -U setuptools - - run: pip install -U -r requirements.txt - - run: pip install -U -r test-requirements.txt - - run: python3 --version - - run: python3 setup.py test - build_bionic: docker: - image: qrledger/qrl-docker-ci:bionic @@ -65,7 +54,7 @@ jobs: integration_fast: docker: - - image: qrledger/qrl-docker-ci:xenial + - image: qrledger/qrl-docker-ci:bionic environment: PYTHONPATH: /root/project:/root/project/tests_integration TESTINPLACE: 1 @@ -79,7 +68,7 @@ jobs: integration_smoke: docker: - - image: qrledger/qrl-docker-ci:xenial + - image: qrledger/qrl-docker-ci:bionic environment: PYTHONPATH: /root/project:/root/project/tests_integration TESTINPLACE: 1 @@ -119,7 +108,7 @@ jobs: deploy-pypi: docker: - - image: qrledger/qrl-docker-ci:xenial + - image: qrledger/qrl-docker-ci:bionic steps: - checkout - run: git submodule update --init --recursive From 1c53ef81799027b895599232862b3a5756241cbd Mon Sep 17 00:00:00 2001 From: cyyber Date: Wed, 16 Nov 2022 20:11:10 +0530 Subject: [PATCH 06/15] Updated pytest version --- setup.cfg | 2 +- test-requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.cfg b/setup.cfg index 39a8301c9..c8e6e6043 100644 --- a/setup.cfg +++ b/setup.cfg @@ -54,7 +54,7 @@ install_requires = # Add here test requirements (semicolon-separated) tests_require = - pytest==7.1.3 + pytest>=7.0.0,<=7.1.3 pytest-cov==4.0.0 pytest-xdist==2.5.0 pytest-flake8==1.0.0 diff --git a/test-requirements.txt b/test-requirements.txt index 039dc5e1e..407f97c5c 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,7 +1,7 @@ # Add requirements only needed for your unittests and during development here. # They will be installed automatically when running `python setup.py test`. # ATTENTION: Don't remove pytest-cov and pytest as they are needed. -pytest==7.1.3 +pytest>=7.0.0,<=7.1.3 pytest-cov==4.0.0 pytest-xdist==2.5.0 flake8==3.5.0 From 117135f40aa5baa3417b6e8d75f8da2ced1728e6 Mon Sep 17 00:00:00 2001 From: cyyber Date: Wed, 16 Nov 2022 21:14:15 +0530 Subject: [PATCH 07/15] Fix for tests_integration --- .circleci/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 6eb7edb4f..73383e461 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -74,6 +74,7 @@ jobs: TESTINPLACE: 1 steps: - checkout + - run: apt-get install -y rsync - run: git submodule update --init --recursive --remote - run: apt install -y python3-venv - run: pip install -U -r requirements.txt From 3d31ec580cd335e0196f49e351c41f2b95889cbb Mon Sep 17 00:00:00 2001 From: cyyber Date: Wed, 16 Nov 2022 22:04:21 +0530 Subject: [PATCH 08/15] added apt-get update for integration_tests --- .circleci/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 73383e461..bbc7ee002 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -74,6 +74,7 @@ jobs: TESTINPLACE: 1 steps: - checkout + - run: apt-get update - run: apt-get install -y rsync - run: git submodule update --init --recursive --remote - run: apt install -y python3-venv From b4e43ebf13ae7e83089d4c8e49754a0c89756edc Mon Sep 17 00:00:00 2001 From: cyyber Date: Thu, 17 Nov 2022 00:52:08 +0530 Subject: [PATCH 09/15] updated mock version restriction in requirements.txt --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 44cac0178..30875f393 100644 --- a/requirements.txt +++ b/requirements.txt @@ -23,5 +23,5 @@ Flask>=2.0.0,<=2.2.2 json-rpc==1.13.0 idna==2.6 cryptography==2.3 -mock==2.0.0 +mock>=2.0.0 daemonize==2.4.7 From dd0cf7ef5c7521f571c3d0fcae2b27ba8145adb4 Mon Sep 17 00:00:00 2001 From: cyyber Date: Thu, 17 Nov 2022 18:06:45 +0530 Subject: [PATCH 10/15] Updated integration_tests --- tests_integration | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests_integration b/tests_integration index cd8d85d30..a9f64e302 160000 --- a/tests_integration +++ b/tests_integration @@ -1 +1 @@ -Subproject commit cd8d85d3085ae8db208faed1262679cb09ac1d33 +Subproject commit a9f64e3021cf0dcd1021c951a4fc6a2c9b83644d From 49deebc20cf81192e3fe587ba958d4b02e90f93b Mon Sep 17 00:00:00 2001 From: cyyber Date: Thu, 17 Nov 2022 18:08:08 +0530 Subject: [PATCH 11/15] Updated mock version in setup.cfg --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index c8e6e6043..8d23a1172 100644 --- a/setup.cfg +++ b/setup.cfg @@ -49,7 +49,7 @@ install_requires = json-rpc==1.13.0 idna==2.6 cryptography==2.3 - mock==2.0.0 + mock>=2.0.0 daemonize==2.4.7 # Add here test requirements (semicolon-separated) From 3dabbf0f162cb3002a22aa30623e3526418124fc Mon Sep 17 00:00:00 2001 From: cyyber Date: Thu, 17 Nov 2022 20:20:27 +0530 Subject: [PATCH 12/15] generate_genesis.py token migration file is now made optional --- src/qrl/tools/generate_genesis.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/qrl/tools/generate_genesis.py b/src/qrl/tools/generate_genesis.py index 4b9044c60..8f8fe242b 100644 --- a/src/qrl/tools/generate_genesis.py +++ b/src/qrl/tools/generate_genesis.py @@ -57,14 +57,12 @@ def get_migration_transactions(signing_xmss, filename): def main(): + exclude_migration_tx = False if len(sys.argv) > 2: print("Unexpected arguments") sys.exit(0) elif len(sys.argv) == 1: - print("Missing Filename") - sys.exit(0) - - filename = sys.argv[1] + exclude_migration_tx = True if sys.version_info.major > 2: seed = bytes(hstr2bin(input('Enter extended hexseed: '))) @@ -73,7 +71,10 @@ def main(): dist_xmss = XMSS.from_extended_seed(seed) - transactions = get_migration_transactions(signing_xmss=dist_xmss, filename=filename) + transactions = [] + if not exclude_migration_tx: + filename = sys.argv[1] + transactions = get_migration_transactions(signing_xmss=dist_xmss, filename=filename) block = Block.create(dev_config=config.dev, block_number=0, From d51460720a9145e4d2d49a03b93167035eb0c783 Mon Sep 17 00:00:00 2001 From: cyyber Date: Thu, 17 Nov 2022 20:21:16 +0530 Subject: [PATCH 13/15] Added new testnet genesis file & config --- src/qrl/network/testnet/config.yml | 13 +++++++++++-- src/qrl/network/testnet/genesis.yml | 6 +++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/qrl/network/testnet/config.yml b/src/qrl/network/testnet/config.yml index c803f740c..9c5118322 100644 --- a/src/qrl/network/testnet/config.yml +++ b/src/qrl/network/testnet/config.yml @@ -1,4 +1,13 @@ peer_list: [ "18.130.83.207", "209.250.246.234", "136.244.104.146", "95.179.154.132" ] -genesis_prev_headerhash: 'Testnet 2022' -genesis_timestamp: 1641963062 +genesis_prev_headerhash: 'Final Testnet' +genesis_timestamp: 1668696491 genesis_difficulty: 5000 +p2p_local_port: 29000 +p2p_public_port: 29000 +admin_api_port: 29008 +public_api_port: 29009 +mining_api_port: 29007 +grpc_proxy_port: 28090 +wallet_daemon_port: 28091 +public_api_server: "127.0.0.1:29009" +wallet_api_port: 29010 \ No newline at end of file diff --git a/src/qrl/network/testnet/genesis.yml b/src/qrl/network/testnet/genesis.yml index a0373b48e..2c6e17fa4 100644 --- a/src/qrl/network/testnet/genesis.yml +++ b/src/qrl/network/testnet/genesis.yml @@ -2,11 +2,11 @@ genesisBalance: - address: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= balance: '105000000000000000' header: - hashHeader: LGyQSHq8RYrEHmG7VVtmc9YIqHNxFIH9IMrUTo34lJ0= - hashHeaderPrev: VGVzdG5ldCAyMDIy + hashHeader: 1nZdbgr36LJbrFhyeZmeFruwvTA4RFk04tnzskqJBB4= + hashHeaderPrev: RmluYWwgVGVzdG5ldA== merkleRoot: wquld1GTaqfKWRhAdWpOeREWVdCmlTYIAUrY9eWGwTQ= rewardBlock: '65000000000000000' - timestampSeconds: '1641963062' + timestampSeconds: '1668696491' transactions: - coinbase: addrTo: AQYA3oDYKOMv8cfAHpT2zewSkuiz/0gYQy5+atQfOsdD57loCQog From 3b4009e5d1f15eadf730ab44bfe6e3d07b1105be Mon Sep 17 00:00:00 2001 From: cyyber Date: Thu, 17 Nov 2022 20:21:54 +0530 Subject: [PATCH 14/15] Updated testnet hardfork block details --- src/qrl/core/config.py | 3 ++- src/qrl/main.py | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/qrl/core/config.py b/src/qrl/core/config.py index f34bc85f1..283dc633b 100644 --- a/src/qrl/core/config.py +++ b/src/qrl/core/config.py @@ -313,7 +313,8 @@ def __init__(self, pbdata, ignore_check=False, ignore_singleton=False): # ====================================== self.hard_fork_heights = [942375, 1938000, 2078800] self.hard_fork_node_disconnect_delay = [0, 0, 2880] - self.testnet_hard_fork_heights = [1, 3000] + self.testnet_hard_fork_heights = [1, 3000, 3000] + self.testnet_hard_fork_node_disconnect_delay = [0, 0, 0] # ====================================== # PROPOSAL CONFIG diff --git a/src/qrl/main.py b/src/qrl/main.py index 6efa24594..27df4462a 100644 --- a/src/qrl/main.py +++ b/src/qrl/main.py @@ -68,7 +68,10 @@ def main(): qrl_dir_post_fix = '' copy_files = [] if args.network_type == 'testnet': - config.dev.hard_fork_heights = list(config.dev.testnet_hard_fork_heights) # Hard Fork Block Height For Testnet + # Hard Fork Block Height For Testnet + config.dev.hard_fork_heights = list(config.dev.testnet_hard_fork_heights) + # Hard Fork Block Height Disconnect Delay For Testnet + config.dev.hard_fork_node_disconnect_delay = list(config.dev.testnet_hard_fork_node_disconnect_delay) qrl_dir_post_fix = '-testnet' package_directory = os.path.dirname(os.path.abspath(__file__)) copy_files.append(os.path.join(package_directory, 'network/testnet/genesis.yml')) From 1945ee86a7e9849ae7f91e1842398ef905f95a5b Mon Sep 17 00:00:00 2001 From: cyyber Date: Thu, 17 Nov 2022 20:28:46 +0530 Subject: [PATCH 15/15] Config for testnet to begin with randomx mining algo --- src/qrl/core/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qrl/core/config.py b/src/qrl/core/config.py index 283dc633b..3342afc07 100644 --- a/src/qrl/core/config.py +++ b/src/qrl/core/config.py @@ -313,7 +313,7 @@ def __init__(self, pbdata, ignore_check=False, ignore_singleton=False): # ====================================== self.hard_fork_heights = [942375, 1938000, 2078800] self.hard_fork_node_disconnect_delay = [0, 0, 2880] - self.testnet_hard_fork_heights = [1, 3000, 3000] + self.testnet_hard_fork_heights = [0, 0, 0] self.testnet_hard_fork_node_disconnect_delay = [0, 0, 0] # ======================================