diff --git a/.github/workflows/komodod_ci.yml b/.github/workflows/komodod_ci.yml index 3543604ce17..0ca796f501c 100644 --- a/.github/workflows/komodod_ci.yml +++ b/.github/workflows/komodod_ci.yml @@ -1,15 +1,11 @@ -name: CI test +name: Komodo CI on: [push, pull_request] jobs: - ci: - name: ${{ matrix.os }} CI - runs-on: ${{ matrix.os }} - - strategy: - matrix: - os: [macOS-latest, ubuntu-18.04] + linux-build: + name: Linux Build + runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 @@ -20,11 +16,17 @@ jobs: sudo apt-get remove php7.1-fpm php7.2-fpm php7.3-fpm php7.3-common sudo apt-get update sudo apt-get upgrade -y - sudo apt-get install build-essential pkg-config libc6-dev m4 g++-multilib autoconf libtool libncurses-dev unzip git python zlib1g-dev wget bsdmainutils automake libboost-all-dev libssl-dev libprotobuf-dev protobuf-compiler libqrencode-dev libdb++-dev ntp ntpdate nano software-properties-common curl libevent-dev libcurl4-gnutls-dev cmake clang libsodium-dev -y - sudo apt-get install python3.6 python3-pip python3-setuptools libgnutls28-dev - pip3 install setuptools wheel - pip3 install slick-bitcoinrpc pytest wget - # params we will need for tests execution + sudo apt-get update + sudo apt-get install -q \ + curl \ + python3 \ + python3-dev \ + python3-setuptools \ + python3-pip \ + libcurl4-openssl-dev \ + libssl-dev -y + python3 -m pip install setuptools wheel + python3 -m pip install slick-bitcoinrpc pytest wget jsonschema ./zcutil/fetch-params.sh - name: Build (Linux) @@ -32,6 +34,13 @@ jobs: run: | # flag for some CC tests transactions - so DO NOT USE THIS CI ARTIFACTS IN PRODUCTION!!! CONFIGURE_FLAGS='CPPFLAGS=-DTESTMODE' ./zcutil/build.sh -j$(nproc) + tar -czvf komodo-linux.tar.gz src/komodod src/komodo-cli + + - name: Upload komodo-linux.tar.gz as artifact + uses: actions/upload-artifact@v1 + with: + name: komodo-linux + path: ./komodo-linux.tar.gz # - name: Install deps (macOS) # if: runner.os == 'macOS' # run: | @@ -56,16 +65,192 @@ jobs: # CONFIGURE_FLAGS='CPPFLAGS=-DTESTMODE' ./zcutil/build-mac.sh -j4 # - name: Run CC tests # if: runner.os == 'Linux' || runner.os == 'macOS' - - name: Run CC tests - if: runner.os == 'Linux' + + + linux-test-dice-token-reards-faucet-cc: + name: Test (Linux/Dice, Token, Faucet, Rewards) + runs-on: ubuntu-latest + needs: linux-build + + steps: + - uses: actions/checkout@v1 + + - name: Install deps (Dice, Token, Faucet, Rewards CC) + run: | + sudo apt-get update + sudo apt-get install -q \ + curl \ + python3 \ + python3-dev \ + python3-setuptools \ + python3-pip \ + libcurl4-openssl-dev \ + libssl-dev -y + python3 -m pip install setuptools wheel + python3 -m pip install slick-bitcoinrpc pytest wget jsonschema + + - name: Download komodo-linux.tar.gz + uses: actions/download-artifact@v1 + with: + name: komodo-linux + + - name: Test CC (Linux) + run: | + mv komodo-linux/komodo-linux.tar.gz . + mkdir -p src + tar xzvf komodo-linux.tar.gz + ./zcutil/fetch-params.sh + cd qa/pytest_komodo + ./ci_setup.sh "cc_modules/test_dice.py cc_modules/test_faucet.py cc_modules/test_token.py cc_modules/test_rewards.py" + + linux-test-oracles: + name: Test (Linux/OraclesCC) + runs-on: ubuntu-latest + needs: linux-build + + steps: + - uses: actions/checkout@v1 + + - name: Install deps (OraclesCC) + run: | + sudo apt-get update + sudo apt-get install -q \ + curl \ + python3 \ + python3-dev \ + python3-setuptools \ + python3-pip \ + libcurl4-openssl-dev \ + libssl-dev -y + python3 -m pip install setuptools wheel + python3 -m pip install slick-bitcoinrpc pytest wget jsonschema + + - name: Download komodo-linux.tar.gz + uses: actions/download-artifact@v1 + with: + name: komodo-linux + + - name: Oracles Test (Linux) + run: | + mv komodo-linux/komodo-linux.tar.gz . + mkdir -p src + tar xzvf komodo-linux.tar.gz + ./zcutil/fetch-params.sh + cd qa/pytest_komodo + ./ci_setup.sh cc_modules/test_oracles.py + + linux-test-baserpc: + name: Test (Linux/BasicRPC) + runs-on: ubuntu-latest + needs: linux-build + + steps: + - uses: actions/checkout@v1 + + - name: Install deps (BasicRPC) + run: | + sudo apt-get update + sudo apt-get install -q \ + curl \ + python3 \ + python3-dev \ + python3-setuptools \ + python3-pip \ + libcurl4-openssl-dev \ + libssl-dev -y + python3 -m pip install setuptools wheel + python3 -m pip install slick-bitcoinrpc pytest wget jsonschema + + - name: Download komodo-linux.tar.gz + uses: actions/download-artifact@v1 + with: + name: komodo-linux + + - name: BasicRPC Test (Linux) + run: | + mv komodo-linux/komodo-linux.tar.gz . + mkdir -p src + tar xzvf komodo-linux.tar.gz + ./zcutil/fetch-params.sh + cd qa/pytest_komodo + ./ci_setup.sh basic + + linux-test-channels: + name: Test (Linux/ChannelsCC) + runs-on: ubuntu-latest + needs: linux-build + + steps: + - uses: actions/checkout@v1 + + - name: Install deps (ChannelsCC) + run: | + sudo apt-get update + sudo apt-get install -q \ + curl \ + python3 \ + python3-dev \ + python3-setuptools \ + python3-pip \ + libcurl4-openssl-dev \ + libssl-dev -y + python3 -m pip install setuptools wheel + python3 -m pip install slick-bitcoinrpc pytest wget jsonschema + + - name: Download komodo-linux.tar.gz + uses: actions/download-artifact@v1 + with: + name: komodo-linux + + - name: Channels Test (Linux) run: | - cd qa/rpc-tests/cc_pytest - ./ci_setup.sh + mv komodo-linux/komodo-linux.tar.gz . + mkdir -p src + tar xzvf komodo-linux.tar.gz + ./zcutil/fetch-params.sh + cd qa/pytest_komodo + ./ci_setup.sh cc_modules/test_channels.py + + linux-test-heir: + name: Test (Linux/HeirCC) + runs-on: ubuntu-latest + needs: linux-build - job-win-build: + steps: + - uses: actions/checkout@v1 + + - name: Install deps (HeirCC) + run: | + sudo apt-get update + sudo apt-get install -q \ + curl \ + python3 \ + python3-dev \ + python3-setuptools \ + python3-pip \ + libcurl4-openssl-dev \ + libssl-dev -y + python3 -m pip install setuptools wheel + python3 -m pip install slick-bitcoinrpc pytest wget jsonschema + + - name: Download komodo-linux.tar.gz + uses: actions/download-artifact@v1 + with: + name: komodo-linux + + - name: Heir Test (Linux) + run: | + mv komodo-linux/komodo-linux.tar.gz . + mkdir -p src + tar xzvf komodo-linux.tar.gz + ./zcutil/fetch-params.sh + cd qa/pytest_komodo + ./ci_setup.sh cc_modules/test_heir.py + + windows-build: name: Win Build - runs-on: ubuntu-16.04 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 @@ -84,11 +269,10 @@ jobs: rustup target add x86_64-pc-windows-gnu sudo update-alternatives --set x86_64-w64-mingw32-gcc /usr/bin/x86_64-w64-mingw32-gcc-posix sudo update-alternatives --set x86_64-w64-mingw32-g++ /usr/bin/x86_64-w64-mingw32-g++-posix - ./zcutil/build-win.sh -j 2 + CONFIGURE_FLAGS='CPPFLAGS=-DTESTMODE' ./zcutil/build-win.sh -j$(nproc) + zip --junk-paths komodod_win src/komodod.exe - name: Upload komodod.exe as artifact uses: actions/upload-artifact@v1 with: - name: komodod.exe - path: ./src/komodod.exe - - #TODO: windows tests + name: komodod_win + path: ./komodod_win.zip diff --git a/qa/pytest_komodo/README.md b/qa/pytest_komodo/README.md new file mode 100644 index 00000000000..67f2d606fab --- /dev/null +++ b/qa/pytest_komodo/README.md @@ -0,0 +1,40 @@ +Updated RPC unit-tests infrastructure for Antara smart-chain custom modules + +Using pytest as testing framework and slickrpc as rpc proxy. No more python2 support. + +To start just set test nodes RPC credentials in `nodesconfig.json`. +`chainconfig.json` contains daemon start cli params, change hardcoded parameters to use with your chain, default parmas are valid for test bootstrap `http://159.69.45.70/bootstrap.tar.gz` + +`is_fresh_chain=False` param allows to run tests on existing chains (it skips some tests which expecting first CC usage on chain) + +So yes - you can run these tests on existing chains, just RPC creds (and wallets with some balance) needed. + +# Dependencies +On Linux and MacOS: +```bash +pip3 install setuptools wheel slick-bitcoinrpc pytest wget jsonschema +``` +On Windows: +```cmd +pip3 install setuptools wheel python-bitcoinrpc pytest wget jsonschema +``` +slik-bitcoinrpc relies on libcurl and can have issues on Windows machines, thus we suggest to use different Proxy library. + +# Usage + +In `~/komodo/qa/rpc-tests/pytest_rpc` directory: + +`python3 -m pytest basic -s` - starts all basic tests +`python3 -m pytest cc_modules/test_dice.py -s` - starts specific test, dice in this case + +`-s` flag is optional, just displaying python prints which might be helpful in debugging + +`ci_test.sh cc_modules` script will start a all CCs full test suite from bootstrapped chain - best way to start the tests +You still can run specific test via script `ci_test.sh basic/test_utils.py` + +The `start_chains.py` script can spin needed amount of nodes and start the test chain. +You can find an example of this script usage in `ci_setup.sh`. Don't forget to change `test_config.json` accordingly to the chain params. + +On Windows machines use `start_ci.bat` instead of `ci_setup.sh` + +Also there is bootstrap downloading functionality in `start_chains.py` what should be quite useful for automated testing setups diff --git a/qa/pytest_komodo/basic/pytest_util.py b/qa/pytest_komodo/basic/pytest_util.py new file mode 100644 index 00000000000..04a3df66772 --- /dev/null +++ b/qa/pytest_komodo/basic/pytest_util.py @@ -0,0 +1,163 @@ +import time +import jsonschema +import os +try: + from slickrpc import Proxy + from slickrpc.exc import RpcException as RPCError + from pycurl import error as HttpError +except ImportError: + from bitcoinrpc.authproxy import AuthServiceProxy as Proxy + from bitcoinrpc.authproxy import JSONRPCException as RPCError + from http.client import HTTPException as HttpError + + +def create_proxy(node_params_dictionary): + try: + proxy = Proxy("http://%s:%s@%s:%d" % (node_params_dictionary.get('rpc_user'), + node_params_dictionary.get('rpc_password'), + node_params_dictionary.get('rpc_ip'), + node_params_dictionary.get('rpc_port')), timeout=120) + except Exception as e: + raise Exception("Connection error! Probably no daemon on selected port. Error: ", e) + return proxy + + +def validate_proxy(env_params_dictionary, proxy, node=0): + attempts = 0 + while True: # base connection check + try: + getinfo_output = proxy.getinfo() + print(getinfo_output) + break + except Exception as e: + print("Coennction failed, error: ", e, "\nRetrying") + attempts += 1 + time.sleep(2) + if attempts > 15: + raise ChildProcessError("Node ", node, " does not respond") + print("IMPORTING PRIVKEYS") + res = proxy.importprivkey(env_params_dictionary.get('test_wif')[node], '', True) + print(res) + assert proxy.validateaddress(env_params_dictionary.get('test_address')[node])['ismine'] + assert proxy.getinfo()['pubkey'] == env_params_dictionary.get('test_pubkey')[node] + assert proxy.verifychain() + time.sleep(15) + assert proxy.getbalance() > 777 + + +def enable_mining(proxy): + cores = os.cpu_count() + if cores > 2: + threads_count = cores - 2 + else: + threads_count = 1 + tries = 0 + while True: + try: + proxy.setgenerate(True, threads_count) + break + except (RPCError, HttpError) as e: + print(e, " Waiting chain startup\n") + time.sleep(10) + tries += 1 + if tries > 30: + raise ChildProcessError("Node did not start correctly, aborting\n") + + +def mine_and_waitconfirms(txid, proxy): # should be used after tx is send + # we need the tx above to be confirmed in the next block + attempts = 0 + while True: + try: + confirmations_amount = proxy.getrawtransaction(txid, 1)['confirmations'] + break + except KeyError as e: + print("\ntx is in mempool still probably, let's wait a little bit more\nError: ", e) + time.sleep(5) + attempts += 1 + if attempts < 100: + pass + else: + print("\nwaited too long - probably tx stuck by some reason") + return False + if confirmations_amount < 2: + print("\ntx is not confirmed yet! Let's wait a little more") + time.sleep(5) + return True + else: + print("\ntx confirmed") + return True + + +def validate_transaction(proxy, txid, conf_req): + try: + isinstance(proxy, Proxy) + except Exception as e: + raise TypeError("Not a Proxy object, error: " + str(e)) + conf = 0 + while conf < conf_req: + print("\nWaiting confirmations...") + resp = proxy.gettransaction(txid) + conf = resp.get('confirmations') + time.sleep(2) + + +def validate_template(blocktemplate, schema=''): # BIP 0022 + blockschema = { + 'type': 'object', + 'required': ['bits', 'curtime', 'height', 'previousblockhash', 'version', 'coinbasetxn'], + 'properties': { + 'capabilities': {'type': 'array', + 'items': {'type': 'string'}}, + 'version': {'type': ['integer', 'number']}, + 'previousblockhash': {'type': 'string'}, + 'finalsaplingroothash': {'type': 'string'}, + 'transactions': {'type': 'array', + 'items': {'type': 'object'}}, + 'coinbasetxn': {'type': 'object', + 'required': ['data', 'hash', 'depends', 'fee', 'required', 'sigops'], + 'properties': { + 'data': {'type': 'string'}, + 'hash': {'type': 'string'}, + 'depends': {'type': 'array'}, + 'fee': {'type': ['integer', 'number']}, + 'sigops': {'type': ['integer', 'number']}, + 'coinbasevalue': {'type': ['integer', 'number']}, + 'required': {'type': 'boolean'} + } + }, + 'longpollid': {'type': 'string'}, + 'target': {'type': 'string'}, + 'mintime': {'type': ['integer', 'number']}, + 'mutable': {'type': 'array', + 'items': {'type': 'string'}}, + 'noncerange': {'type': 'string'}, + 'sigoplimit': {'type': ['integer', 'number']}, + 'sizelimit': {'type': ['integer', 'number']}, + 'curtime': {'type': ['integer', 'number']}, + 'bits': {'type': 'string'}, + 'height': {'type': ['integer', 'number']} + } + } + if not schema: + schema = blockschema + jsonschema.validate(instance=blocktemplate, schema=schema) + + +def check_synced(*proxies): + for proxy in proxies: + tries = 0 + while True: + check = proxy.getinfo().get('synced') + proxy.ping() + if check: + print("Synced\n") + break + else: + print("Waiting for sync\nAttempt: ", tries + 1, "\n") + time.sleep(10) + tries += 1 + if tries > 120: # up to 20 minutes + return False + return True + diff --git a/qa/pytest_komodo/basic/test_blocks.py b/qa/pytest_komodo/basic/test_blocks.py new file mode 100644 index 00000000000..4117421930a --- /dev/null +++ b/qa/pytest_komodo/basic/test_blocks.py @@ -0,0 +1,387 @@ +#!/usr/bin/env python3 +# Copyright (c) 2019 SuperNET developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import pytest +from pytest_util import validate_transaction +from pytest_util import validate_template +from decimal import * + + +@pytest.mark.usefixtures("proxy_connection") +class TestBlockchainMethods: + + def test_coinsupply(self, test_params): + rpc = test_params.get('node1').get('rpc') + res = rpc.getinfo() + height = res.get('blocks') + # fixed height + res = rpc.coinsupply(str(height)) + assert res.get('result') == 'success' + assert isinstance(res.get('height'), int) + # coinsupply without value should return max height + res = rpc.coinsupply() + assert res.get('result') == 'success' + assert isinstance(res.get('height'), int) + # invalid height + res = rpc.coinsupply("-1") + assert res.get('error') == "invalid height" + # invalid value + res = rpc.coinsupply("aaa") + assert res.get('error') == "couldnt calculate supply" + + def test_getbestblockhash(self, test_params): + rpc = test_params.get('node1').get('rpc') + res = rpc.getbestblockhash() + assert isinstance(res, str) + + def test_getblock(self, test_params): + schema = { + 'type': 'object', + 'properties': { + 'last_notarized_height': {'type': 'integer'}, + 'hash': {'type': 'string'}, + 'confirmations': {'type': 'integer'}, + 'rawconfirmations': {'type': 'integer'}, + 'size': {'type': 'integer'}, + 'height': {'type': 'integer'}, + 'version': {'type': 'integer'}, + 'merkleroot': {'type': 'string'}, + 'segid': {'type': 'integer'}, + 'finalsaplingroot': {'type': 'string'}, + 'tx': {'type': 'array'}, + 'time': {'type': 'integer'}, + 'nonce': {'type': 'string'}, + 'solution': {'type': 'string'}, + 'bits': {'type': 'string'}, + 'difficulty': {'type': ['number', 'integer']}, + 'chainwork': {'type': 'string'}, + 'blocktype': {'type': 'string'}, + 'valuePools': { + 'type': 'array', + 'items': { + 'type': 'object', + 'properties': { + 'id': {'type': 'string'}, + 'monitored': {'type': 'boolean'}, + 'chainValue': {'type': ['number', 'integer']}, + 'chainValueZat': {'type': ['number', 'integer']}, + 'valueDelta': {'type': ['number', 'integer']}, + 'valueDeltaZat': {'type': ['number', 'integer']} + } + } + }, + 'previousblockhash': {'type': 'string'}, + 'nextblockhash': {'type': 'string'} + }, + 'required': ['last_notarized_height', 'hash', 'confirmations', 'rawconfirmations', 'size', 'height', + 'version', 'merkleroot', 'segid', 'finalsaplingroot', 'tx', 'time', 'nonce', 'solution', + 'bits', 'difficulty', 'chainwork', 'anchor', 'blocktype', 'valuePools', + 'previousblockhash'] + } + rpc = test_params.get('node1').get('rpc') + blocknum = str(rpc.getblockcount()) + res = rpc.getblock(blocknum) + validate_template(res, schema) + res = rpc.getblock(blocknum, False) + assert isinstance(res, str) + + def test_getblockchaininfo(self, test_params): + schema = { + 'type': 'object', + 'required': ['chain', 'blocks', 'synced', 'headers', 'bestblockhash', 'upgrades', 'consensus', + 'difficulty', 'verificationprogress', 'chainwork', 'pruned', 'commitments'], + 'properties': { + 'chain': {'type': 'string'}, + 'blocks': {'type': 'integer'}, + 'synced': {'type': 'boolean'}, + 'headers': {'type': 'integer'}, + 'bestblockhash': {'type': 'string'}, + 'difficulty': {'type': ['integer', 'number']}, + 'verificationprogress': {'type': ['integer', 'number']}, + 'chainwork': {'type': 'string'}, + 'pruned': {'type': 'boolean'}, + 'commitments': {'type': ['integer', 'number']}, + 'valuePools': {'type': 'array', + 'items': {'type': 'object'}}, + 'upgrades': {'type': 'object'}, + 'consensus': {'type': 'object'} + } + } + rpc = test_params.get('node1').get('rpc') + res = rpc.getblockchaininfo() + validate_template(res, schema) + + def test_getblockcount(self, test_params): + rpc = test_params.get('node1').get('rpc') + res = rpc.getinfo() + height = res.get('blocks') + res = rpc.getblockcount() + assert res == height + + def test_getblockhash(self, test_params): + rpc = test_params.get('node1').get('rpc') + res = rpc.getinfo() + height = res.get('blocks') + res = rpc.getblockhash(height) + assert isinstance(res, str) + + # + # timestampindex -- required param + # + # def test_getblockhashes(self, test_params): + # test_values = { + # 'high': 101, + # 'low': 99, + # 'options': '{"noOrphans":false, "logicalTimes":true}' + # } + # rpc = test_params.get('node1').get('rpc') + # res = rpc.getblockhashes(test_values['high'], test_values['low'], test_values['options']) + + def test_getblockheader(self, test_params): + schema = { + 'type': 'object', + 'properties': { + 'last_notarized_height': {'type': 'integer'}, + 'hash': {'type': 'string'}, + 'confirmations': {'type': 'integer'}, + 'rawconfirmations': {'type': 'integer'}, + 'height': {'type': 'integer'}, + 'version': {'type': 'integer'}, + 'merkleroot': {'type': 'string'}, + 'segid': {'type': 'integer'}, + 'finalsaplingroot': {'type': 'string'}, + 'time': {'type': 'integer'}, + 'nonce': {'type': 'string'}, + 'solution': {'type': 'string'}, + 'bits': {'type': 'string'}, + 'difficulty': {'type': ['number', 'integer']}, + 'chainwork': {'type': 'string'}, + 'previousblockhash': {'type': 'string'}, + 'nextblockhash': {'type': 'string'} + }, + 'required': ['last_notarized_height', 'hash', 'confirmations', 'rawconfirmations', + 'height', 'version', 'merkleroot', 'segid', 'finalsaplingroot', 'time', + 'nonce', 'solution', 'bits', 'difficulty', 'chainwork', 'previousblockhash'] + } + rpc = test_params.get('node1').get('rpc') + res = rpc.getinfo() + block = res.get('blocks') + blockhash = rpc.getblockhash(block) + res1 = rpc.getblockheader(blockhash) + res2 = rpc.getblockheader(blockhash, True) + assert res1 == res2 + validate_template(res1, schema) + res = rpc.getblockheader(blockhash, False) + assert isinstance(res, str) + + def test_getchaintips(self, test_params): + schema = { + 'type': 'array', + 'items': { + 'type': 'object', + 'required': ['height', 'hash', 'branchlen', 'status'], + 'properties': { + 'height': {'type': 'integer'}, + 'hash': {'type': 'string'}, + 'branchlen': {'type': 'integer'}, + 'status': {'type': 'string'} + } + } + } + rpc = test_params.get('node1').get('rpc') + res = rpc.getchaintips() + validate_template(res, schema) + + def test_getchaintxstats(self, test_params): + schema = { + 'type': 'object', + 'properties': { + 'time': {'type': 'integer'}, + 'txcount': {'type': 'integer'}, + 'window_final_block_hash': {'type': 'string'}, + 'window_final_block_count': {'type': 'integer'}, + 'window_block_count': {'type': 'integer'}, + 'window_tx_count': {'type': 'integer'}, + 'window_interval': {'type': 'integer'}, + 'txrate': {'type': ['number', 'integer']} + }, + 'required': ['time', 'txcount', 'txrate', 'window_final_block_hash', 'window_interval', + 'window_block_count', 'window_tx_count'] + } + rpc = test_params.get('node1').get('rpc') + res = rpc.getchaintxstats() + validate_template(res, schema) + res = rpc.getinfo() + nblocks = (int(res.get('blocks') / 2)) + blockhash = rpc.getblockhash(res.get('blocks')) + res = rpc.getchaintxstats(nblocks) + validate_template(res, schema) + res = rpc.getchaintxstats(nblocks, blockhash) + validate_template(res, schema) + + def test_getdifficulty(self, test_params): + rpc = test_params.get('node1').get('rpc') + res = rpc.getdifficulty() + # python-bitcoinrpc Proxy can return value as decimal + assert isinstance(res, float) or isinstance(res, int) or isinstance(res, Decimal) + + # + # Only applies to -ac_staked Smart Chains + # + # def test_(self, test_params): + # test_values = {} + + def test_getmempoolinfo(self, test_params): + rpc = test_params.get('node1').get('rpc') + res = rpc.getmempoolinfo() + assert isinstance(res.get('size'), int) + assert isinstance(res.get('bytes'), int) + assert isinstance(res.get('usage'), int) + + # + # The method requires spentindex to be enabled. + # txid 68ee9d23ba51e40112be3957dd15bc5c8fa9a751a411db63ad0c8205bec5e8a1 + # + # def test_getspentinfo(self, test_params): + # pass + + def test_gettxout(self, test_params): + schema = { + 'type': 'object', + 'properties': { + 'bestblock': {'type': 'string'}, + 'confirmations': {'type': 'integer'}, + 'rawconfirmations': {'type': 'integer'}, + 'value': {'type': 'number'}, + 'scriptPubKey': { + 'type': 'object', + 'properties': { + 'asm': {'type': 'string'}, + 'hex': {'type': 'string'}, + 'reqSigs': {'type': 'integer'}, + 'type': {'type': 'string'}, + 'addresses': { + 'type': 'array', + 'items': {'type': 'string'} + } + } + }, + 'version': {'type': 'integer'}, + 'coinbase': {'type': 'boolean'} + }, + 'required': ['bestblock', 'confirmations', 'rawconfirmations', + 'value', 'scriptPubKey', 'version', 'coinbase'] + } + rpc = test_params.get('node1').get('rpc') + res = rpc.listunspent() + txid = res[0].get('txid') + vout = res[0].get('vout') + res = rpc.gettxout(txid, vout) + validate_template(res, schema) + res = rpc.gettxout(txid, -1) + assert not res # gettxout retuns None when vout does not exist + + def test_gettxoutproof(self, test_params): + rpc = test_params.get('node1').get('rpc') + res = rpc.listunspent() + txid = res[0].get('txid') + res = rpc.gettxoutproof([txid]) + assert isinstance(res, str) + + def test_gettxoutsetinfo(self, test_params): + schema = { + 'type': 'object', + 'required': ['height', 'bestblock', 'transactions', 'txouts', 'bytes_serialized', + 'hash_serialized', 'total_amount'], + 'properties': { + 'height': {'type': 'integer'}, + 'bestblock': {'type': 'string'}, + 'transactions': {'type': 'integer'}, + 'txouts': {'type': 'integer'}, + 'bytes_serialized': {'type': 'integer'}, + 'hash_serialized': {'type': 'string'}, + 'total_amount': {'type': ['integer', 'number']} + } + } + rpc = test_params.get('node1').get('rpc') + res = rpc.gettxoutsetinfo() + validate_template(res, schema) + + def test_kvupdate(self, test_params): + test_values = { + 'v_key': 'valid_key', + 'value': 'test+value', + 'days': '2', + 'pass': 'secret', + 'n_key': 'invalid_key', + 'keylen': 9 + } + rpc = test_params.get('node1').get('rpc') + res = rpc.kvupdate(test_values['v_key'], test_values['value'], test_values['days'], test_values['pass']) + assert res.get('key') == test_values['v_key'] + assert res.get('keylen') == test_values['keylen'] + assert res.get('value') == test_values['value'] + + def test_getrawmempool(self, test_params): + test_values = { + 'key': 'mempool_key', + 'value': 'key_value', + 'days': '1', + 'pass': 'secret' + } # to get info into mempool, we need to create tx, kvupdate call creates one for us + rpc = test_params.get('node1').get('rpc') + res = rpc.kvupdate(test_values['key'], test_values['value'], test_values['days'], test_values['pass']) + txid = res.get('txid') + kvheight = res.get('height') + res = rpc.getrawmempool() + assert txid in res + res = rpc.getrawmempool(False) # False is default value, res should be same as in call above + assert txid in res + res = rpc.getrawmempool(True) + assert res.get(txid).get('height') == kvheight + + def test_kvsearch(self, test_params): + test_values = { + 'key': 'search_key', + 'value': 'search_value', + 'days': '1', + 'pass': 'secret' + } + rpc = test_params.get('node1').get('rpc') + res = rpc.kvupdate(test_values['key'], test_values['value'], test_values['days'], test_values['pass']) + txid = res.get('txid') + keylen = res.get('keylen') + validate_transaction(rpc, txid, 1) # wait for block + res = rpc.kvsearch(test_values['key']) + assert res.get('key') == test_values['key'] + assert res.get('keylen') == keylen + assert res.get('value') == test_values['value'] + + def test_notaries(self, test_params): + rpc = test_params.get('node1').get('rpc') + res = rpc.notaries('1') + assert res.get('notaries') # call should return list of notary nodes disregarding blocknum + + def test_minerids(self, test_params): + test_values = { + 'error': "couldnt extract minerids" + } + rpc = test_params.get('node1').get('rpc') + res = rpc.minerids('1') + assert res.get('error') == test_values['error'] or isinstance(res.get('mined'), list) + # likely to fail on bootstrap test chains + + def test_verifychain(self, test_params): + rpc = test_params.get('node1').get('rpc') + res = rpc.verifychain() + assert res # rpc returns True if chain was verified + + def test_verifytxoutproof(self, test_params): + rpc = test_params.get('node1').get('rpc') + res = rpc.listunspent() + txid = res[0].get('txid') + txproof = rpc.gettxoutproof([txid]) + res = rpc.verifytxoutproof(txproof) + assert res[0] == txid diff --git a/qa/pytest_komodo/basic/test_control_address.py b/qa/pytest_komodo/basic/test_control_address.py new file mode 100644 index 00000000000..df0ecaa8828 --- /dev/null +++ b/qa/pytest_komodo/basic/test_control_address.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +# Copyright (c) 2019 SuperNET developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import pytest +import json + + +@pytest.mark.usefixtures("proxy_connection") +class TestControlAddress: + + def test_getinfo(self, test_params): + rpc1 = test_params.get('node1').get('rpc') + res = rpc1.getinfo() + assert res.get('blocks') + assert not res.get('errors') + rpc2 = test_params.get('node2').get('rpc') + res = rpc2.getinfo() + assert res.get('blocks') + assert not res.get('errors') + + def test_help(self, test_params): + rpc = test_params.get('node1').get('rpc') + res = rpc.help() + assert "cclib method [evalcode] [JSON params]" in res diff --git a/qa/pytest_komodo/basic/test_network_mining.py b/qa/pytest_komodo/basic/test_network_mining.py new file mode 100644 index 00000000000..8a100f07165 --- /dev/null +++ b/qa/pytest_komodo/basic/test_network_mining.py @@ -0,0 +1,228 @@ +#!/usr/bin/env python3 +# Copyright (c) 2019 SuperNET developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import pytest +import time +from decimal import * +from pytest_util import validate_template + + +@pytest.mark.usefixtures("proxy_connection") +class TestNetworkMining: + + def test_generate(self, test_params): # generate, getgenerate, setgenerate calls + rpc = test_params.get('node1').get('rpc') + res = rpc.setgenerate(False, -1) + assert not res + res = rpc.getgenerate() + assert not res.get('generate') + assert res.get('numthreads') == -1 + res = rpc.setgenerate(True, 1) + assert not res + res = rpc.getgenerate() + assert res.get('generate') + assert res.get('numthreads') == 1 + # rpc.generate(2) -- requires regtest mode + + def test_getmininginfo(self, test_params): + rpc = test_params.get('node1').get('rpc') + res = rpc.getinfo() + blocks = res.get('blocks') + rpc.setgenerate(True, 1) + res = rpc.getmininginfo() + assert res.get('blocks') == blocks + assert res.get('generate') + assert res.get('numthreads') == 1 + + def test_getblocksubsidy(self, test_params): + schema = { + 'type': 'object', + 'properties': { + 'miner': {'type': 'number'} + } + } + rpc = test_params.get('node1').get('rpc') + res = rpc.getblocksubsidy() + validate_template(res, schema) + res = rpc.getinfo() + block = res.get('blocks') + res = rpc.getblocksubsidy(block) + validate_template(res, schema) + + def test_getblocktemplate(self, test_params): + rpc = test_params.get('node1').get('rpc') + res = rpc.getblocktemplate() + validate_template(res) + + def test_getlocalsolps(self, test_params): + rpc = test_params.get('node1').get('rpc') + res = rpc.getlocalsolps() + # python-bitcoinrpc Proxy can return value as decimal + assert isinstance(res, float) or isinstance(res, int) or isinstance(res, Decimal) + + # getnetworkhashps call is deprecated + + def test_getnetworksolps(self, test_params): + rpc = test_params.get('node1').get('rpc') + res = rpc.getnetworksolps() + assert isinstance(res, float) or isinstance(res, int) + + def test_prioritisetransaction(self, test_params): + rpc = test_params.get('node1').get('rpc') + res = rpc.prioritisetransaction("68ee9d23ba51e40112be3957dd15bc5c8fa9a751a411db63ad0c8205bec5e8a1", 0.0, 10000) + assert res # returns True on success + + def test_submitblock(self, test_params): + rpc = test_params.get('node1').get('rpc') + res = rpc.getinfo() + height = res.get('blocks') + block = rpc.getblock(str(height), False) + res = rpc.submitblock(block) + assert res == 'duplicate' + + def test_getnetworkinfo(self, test_params): + schema = { + 'type': 'object', + 'properties': { + 'version': {'type': 'integer'}, + 'subversion': {'type': 'string'}, + 'protocolversion': {'type': 'integer'}, + 'localservices': {'type': 'string'}, + 'timeoffset': {'type': 'integer'}, + 'connections': {'type': 'integer'}, + 'networks': { + 'type': 'array', + 'items': { + 'type': 'object', + 'properties': { + 'name': {'type': 'string'}, + 'limited': {'type': 'boolean'}, + 'reachable': {'type': 'boolean'}, + 'proxy': {'type': 'string'}, + 'proxy_randomize_credentials': {'type': 'boolean'}, + } + } + }, + 'relayfee': {'type': 'number'}, + 'localadresses': { + 'type': 'array', + 'items': { + 'type': 'object', + 'properties': { + 'address': {'type': 'string'}, + 'port': {'type': 'integer'}, + 'score': {'type': 'integer'}, + }, + 'required': ['address', 'port', 'score'] + } + }, + 'warnings': {'type': 'string'} + }, + 'required': ['version', 'subversion', 'protocolversion', 'localservices', 'timeoffset', 'connections', + 'networks', 'relayfee', 'localaddresses', 'warnings'] + } + rpc = test_params.get('node1').get('rpc') + res = rpc.getnetworkinfo() + validate_template(res, schema) + + def test_getconnectioncount(self, test_params): + rpc = test_params.get('node1').get('rpc') + res = rpc.getconnectioncount() + assert isinstance(res, int) + + def test_getpeerinfo(self, test_params): + schema = { + 'type': 'array', + 'items': { + 'type': 'object', + 'properties': { + 'id': {'type': 'integer'}, + 'addr': {'type': 'string'}, + 'addrlocal': {'type': 'string'}, + 'services': {'type': 'string'}, + 'lastsend': {'type': 'integer'}, + 'lastrecv': {'type': 'integer'}, + 'bytessent': {'type': 'integer'}, + 'bytesrecv': {'type': 'integer'}, + 'conntime': {'type': 'integer'}, + 'timeoffset': {'type': 'integer'}, + 'pingtime': {'type': ['number', 'integer']}, + 'pingwait': {'type': ['number', 'integer']}, + 'version': {'type': 'integer'}, + 'subver': {'type': 'string'}, + 'inbound': {'type': 'boolean'}, + 'startingheight': {'type': 'integer'}, + 'banscore': {'type': ['number', 'integer']}, + 'synced_headers': {'type': 'integer'}, + 'synced_blocks': {'type': 'integer'}, + 'inflight': {'type': 'array'}, + 'whitelisted': {'type': 'boolean'}, + 'number': {'type': 'integer'} + } + } + } + rpc = test_params.get('node1').get('rpc') + res = rpc.getpeerinfo() + validate_template(res, schema) + + def test_getnettotals(self, test_params): + rpc = test_params.get('node1').get('rpc') + res = rpc.getnettotals() + assert isinstance(res.get('timemillis'), int) + + def test_ping(self, test_params): + rpc = test_params.get('node1').get('rpc') + res = rpc.ping() + assert not res # ping call has empty response + + def test_getdeprecationinfo(self, test_params): + rpc = test_params.get('node1').get('rpc') + res = rpc.getdeprecationinfo() + assert isinstance(res.get('version'), int) + + def test_addnode(self, test_params): + rpc = test_params.get('node2').get('rpc') + cnode_ip = test_params.get('node1').get('rpc_ip') + connect_node = (cnode_ip + ':' + str(test_params.get('node1').get('net_port'))) + res = rpc.addnode(connect_node, 'remove') + assert not res + res = rpc.addnode(connect_node, 'onetry') + assert not res + res = rpc.addnode(connect_node, 'add') + assert not res + rpc.ping() + + def test_disconnectnode(self, test_params): + rpc = test_params.get('node2').get('rpc') + cnode_ip = test_params.get('node1').get('rpc_ip') + disconnect_node = (cnode_ip + ':' + str(test_params.get('node1').get('net_port'))) + rpc.addnode(disconnect_node, 'remove') # remove node from addnode list to prevent reconnection + res = rpc.disconnectnode(disconnect_node) # has empty response + assert not res + time.sleep(15) # time to stop node connection + res = rpc.getpeerinfo() + for peer in res: + assert peer.get('addr') != disconnect_node + rpc.addnode(disconnect_node, 'add') + time.sleep(10) # wait for 2nd to reconnect after test + + def test_ban(self, test_params): # setban, listbanned, clearbanned calls + rpc = test_params.get('node1').get('rpc') + ban_list = ['144.144.140.0/255.255.255.0', '144.144.140.12/255.255.255.255', '192.168.0.0/255.255.0.0'] + res = rpc.clearbanned() + assert not res + res = rpc.setban(ban_list[0], 'add', 64800) + assert not res + res = rpc.setban(ban_list[1], 'add', 64800) + assert not res + res = rpc.setban(ban_list[2], 'add', 64800) + assert not res + res = rpc.listbanned() + for peer in res: + node = peer.get('address') + assert node in ban_list + rpc.clearbanned() + res = rpc.listbanned() + assert not res diff --git a/qa/pytest_komodo/basic/test_rawtransactions.py b/qa/pytest_komodo/basic/test_rawtransactions.py new file mode 100644 index 00000000000..9847eda8bc3 --- /dev/null +++ b/qa/pytest_komodo/basic/test_rawtransactions.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python3 +# Copyright (c) 2019 SuperNET developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import pytest +from decimal import * +from pytest_util import validate_template, mine_and_waitconfirms + + +@pytest.mark.usefixtures("proxy_connection") +class TestRawTransactions: + + def test_rawtransactions(self, test_params): # create, fund, sign, send calls + fund_schema = { + 'type': 'object', + 'properties': { + 'hex': {'type': 'string'}, + 'fee': {'type': ['integer', 'number']}, + 'changepos': {'type': ['integer', 'number']} + } + } + sign_schema = { + 'type': 'object', + 'properties': { + 'hex': {'type': 'string'}, + 'complete': {'type': 'boolean'} + } + } + rpc = test_params.get('node1').get('rpc') + res = rpc.listunspent() + txid = res[0].get('txid') + vout = res[0].get('vout') + base_amount = res[0].get('amount') + if isinstance(base_amount, Decimal): + amount = float(base_amount) * 0.9 + print(amount) + else: + amount = base_amount * 0.9 + address = rpc.getnewaddress() + ins = [{'txid': txid, 'vout': vout}] + outs = {address: amount} + + rawtx = rpc.createrawtransaction(ins, outs) + assert isinstance(rawtx, str) + + fundtx = rpc.fundrawtransaction(rawtx) + validate_template(fundtx, fund_schema) + + signtx = rpc.signrawtransaction(fundtx.get('hex')) + validate_template(signtx, sign_schema) + assert signtx['complete'] + + sendtx = rpc.sendrawtransaction(signtx.get('hex')) + assert isinstance(sendtx, str) + assert mine_and_waitconfirms(sendtx, rpc) + + def test_getrawtransaction(self, test_params): # decode, get methods + txschema = { + 'type': 'object', + 'properties': { + 'hex': {'type': 'string'}, + 'txid': {'type': 'string'}, + 'overwintered': {'type': 'boolean'}, + 'version': {'type': 'integer'}, + 'versiongroupid': {'type': 'string'}, + 'locktime': {'type': 'integer'}, + 'expiryheight': {'type': 'integer'}, + 'vin': { + 'type': 'array', + 'items': { + 'type': 'object', + 'properties': { + 'coinbase': {'type': 'string'}, + 'txid': {'type': 'string'}, + 'vout': {'type': 'integer'}, + 'address': {'type': 'string'}, + 'scriptSig': { + 'type': 'object', + 'properties': { + 'asm': {'type': 'string'}, + 'hex': {'type': 'string'} + } + }, + 'value': {'type': ['integer', 'number']}, + 'valueSat': {'type': 'integer'}, + 'sequence': {'type': 'integer'} + } + } + }, + 'vout': { + 'type': 'array', + 'items': { + 'type': 'object', + 'properties': { + 'value': {'type': ['integer', 'number']}, + 'valueSat': {'type': 'integer'}, + 'interest': {'type': ['integer', 'number']}, + 'n': {'type': 'integer'}, + 'scriptPubKey': { + 'type': 'object', + 'properties': { + 'asm': {'type': 'string'}, + 'hex': {'type': 'string'}, + 'reqSigs': {'type': 'integer'}, + 'type': {'type': 'string'}, + 'addresses': {'type': 'array', 'items': {'type': 'string'}} + } + } + } + } + }, + 'vjoinsplit': {'type': 'array'}, + 'valueBalance': {'type': 'number'}, + 'vShieldedSpend': {'type': 'array'}, + 'vShieldedOutput': {'type': 'array'}, + 'blockhash': {'type': 'string'}, + 'height': {'type': 'integer'}, + 'confirmations': {'type': 'integer'}, + 'rawconfirmations': {'type': 'integer'}, + 'time': {'type': 'integer'}, + 'blocktime': {'type': 'integer'} + } + } + scriptschema = { + 'type': 'object', + 'properties': { + 'asm': {'type': 'string'}, + 'hex': {'type': 'string'}, + 'type': {'type': 'string'}, + 'reqSigs': {'type': 'integer'}, + 'address': {'type': 'string'}, + 'p2sh': {'type': 'string'}, + 'addresses': {'type': 'array', + 'items': {'type': 'string'}} + } + } + rpc = test_params.get('node1').get('rpc') + res = rpc.listunspent() + txid = res[0].get('txid') + rawhex = rpc.getrawtransaction(txid) + assert isinstance(rawhex, str) + res = rpc.getrawtransaction(txid, 1) + validate_template(res, txschema) + + scripthex = res.get('vout')[0].get('scriptPubKey').get('hex') + res = rpc.decodescript(scripthex) + validate_template(res, scriptschema) + + res = rpc.decoderawtransaction(rawhex) + validate_template(res, txschema) diff --git a/qa/pytest_komodo/basic/test_shielded.py b/qa/pytest_komodo/basic/test_shielded.py new file mode 100644 index 00000000000..d943eee8405 --- /dev/null +++ b/qa/pytest_komodo/basic/test_shielded.py @@ -0,0 +1,227 @@ +#!/usr/bin/env python3 +# Copyright (c) 2019 SuperNET developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import pytest +import time +from decimal import * +from pytest_util import validate_template, check_synced, mine_and_waitconfirms + + +@pytest.mark.usefixtures("proxy_connection") +class TestZcalls: + + def test_z_getnewaddress(self, test_params): + rpc = test_params.get('node1').get('rpc') + res = rpc.z_getnewaddress() + assert isinstance(res, str) + # test sendmany, operationstatus, operationresult and listreceivedbyaddress calls + + def test_z_send(self, test_params): + schema = { + 'type': 'array', + 'items': { + 'type': 'object', + 'properties': { + 'id': {'type': 'string'}, + 'status': {'type': 'string'}, + 'creation_time': {'type': 'integer'}, + 'execution_secs': {'type': ['number', 'integer']}, + 'method': {'type': 'string'}, + 'error': { + 'type': 'object', + 'properties': { + 'code': {'type': 'integer'}, + 'message': {'type': 'string'} + } + }, + 'result': { + 'type': 'object', + 'properties': {'txid': {'type': 'string'}} + }, + 'params': { + 'type': 'object', + 'properties': { + 'fromaddress': {'type': 'string'}, + 'minconf': {'type': 'integer'}, + 'fee': {'type': ['number', 'integer']}, + 'amounts': { + 'type': 'array', + 'items': { + 'type': 'object', + 'properties': { + 'address': {'type': 'string'}, + 'amount': {'type': ['integer', 'number']} + } + } + } + } + } + } + } + } + schema_list = { + 'type': 'array', + 'items': { + 'type': 'object', + 'properties': { + 'txid': {'type': 'string'}, + 'memo': {'type': 'string'}, + 'amount': {'type': ['number', 'integer']}, + 'change': {'type': 'boolean'}, + 'outindex': {'type': 'integer'}, + 'confirmations': {'type': 'integer'}, + 'rawconfirmations': {'type': 'integer'}, + 'jsoutindex': {'type': 'integer'}, + 'jsindex': {'type': 'integer'} + } + } + } + rpc1 = test_params.get('node1').get('rpc') + rpc2 = test_params.get('node2').get('rpc') + transparent1 = rpc1.getnewaddress() + shielded1 = rpc1.z_getnewaddress() + transparent2 = rpc2.getnewaddress() + shielded2 = rpc2.z_getnewaddress() + amount1 = float("{0:.8f}".format(rpc1.listunspent()[-1].get('amount') / 10)) + amount2 = float("{0:.8f}".format(amount1 / 10)) + try: + import slickrpc + authproxy = 0 + except ImportError: + authproxy = 1 + if authproxy: # type correction when using python-bitcoinrpc Proxy + amount1 = float(amount1) + amount2 = float(amount2) + # python float() is double precision floating point number, + # where z_sendmany expects regural float (8 digits) value + # "{0:.8f}".format(value)) returns number string with 8 digit precision and float() corrects the type + t_send1 = [{'address': transparent1, 'amount': float("{0:.8f}".format(amount2))}] + t_send2 = [{'address': transparent2, 'amount': float("{0:.8f}".format(amount2 * 0.4))}] + z_send1 = [{'address': shielded1, 'amount': float("{0:.8f}".format(amount2 * 0.95))}] + z_send2 = [{'address': shielded2, 'amount': float("{0:.8f}".format(amount2 * 0.4))}] + cases = [(transparent1, t_send1), (transparent1, z_send1), (shielded1, t_send2), (shielded1, z_send2)] + # sendmany cannot use coinbase tx vouts + txid = rpc1.sendtoaddress(transparent1, amount1) + mine_and_waitconfirms(txid, rpc1) + for case in cases: + assert check_synced(rpc1) # to perform z_sendmany nodes should be synced + opid = rpc1.z_sendmany(case[0], case[1]) + assert isinstance(opid, str) + attempts = 0 + while True: + res = rpc1.z_getoperationstatus([opid]) + validate_template(res, schema) + status = res[0].get('status') + if status == 'success': + print('Operation successfull\nWaiting confirmations\n') + res = rpc1.z_getoperationresult([opid]) # also clears op from memory + validate_template(res, schema) + txid = res[0].get('result').get('txid') + time.sleep(30) + tries = 0 + while True: + try: + res = rpc1.getrawtransaction(txid, 1) + confirms = res['confirmations'] + print('TX confirmed \nConfirmations: ', confirms) + break + except Exception as e: + print("\ntx is in mempool still probably, let's wait a little bit more\nError: ", e) + time.sleep(5) + tries += 1 + if tries < 100: + pass + else: + print("\nwaited too long - probably tx stuck by some reason") + return False + break + else: + attempts += 1 + print('Waiting operation result\n') + time.sleep(10) + if attempts >= 100: + print('operation takes too long, aborting\n') + return False + res = rpc1.z_listreceivedbyaddress(shielded1) + validate_template(res, schema_list) + + def test_z_getbalance(self, test_params): + rpc = test_params.get('node1').get('rpc') + zaddr = rpc.z_getnewaddress() + res = rpc.z_getbalance(zaddr, 1) + assert isinstance(res, float) or isinstance(res, int) or isinstance(res, Decimal) + + def test_z_gettotalbalance(self, test_params): + schema = { + 'type': 'object', + 'properties': { + 'transparent': {'type': ['string']}, + 'interest': {'type': ['string']}, + 'private': {'type': ['string']}, + 'total': {'type': ['string']}, + } + } + rpc = test_params.get('node1').get('rpc') + res = rpc.z_gettotalbalance(1) + validate_template(res, schema) + + def test_z_export_viewing_key(self, test_params): + rpc = test_params.get('node1').get('rpc') + zaddr = rpc.z_getnewaddress() + res = rpc.z_exportkey(zaddr) + assert isinstance(res, str) + res = rpc.z_exportviewingkey(zaddr) + assert isinstance(res, str) + + def test_z_import_viewing_key(self, test_params): + rpc1 = test_params.get('node1').get('rpc') + rpc2 = test_params.get('node2').get('rpc') + zaddr = rpc2.z_getnewaddress() + zkey = rpc2.z_exportkey(zaddr) + zvkey = rpc2.z_exportviewingkey(zaddr) + # res = rpc1.z_importviewingkey(zvkey) # https://github.com/zcash/zcash/issues/3060 + # assert not res + res = rpc1.z_importkey(zkey) + assert not res + + def test_z_listaddresses(self, test_params): + schema = { + 'type': 'array', + 'items': { + 'type': 'string' + } + } + rpc = test_params.get('node1').get('rpc') + res = rpc.z_listaddresses() + validate_template(res, schema) + + def test_z_listopertaionsids(self, test_params): + schema = { + 'type': 'array', + 'items': { + 'type': 'string' + } + } + rpc = test_params.get('node1').get('rpc') + res = rpc.z_listoperationids() + validate_template(res, schema) + res = rpc.z_listoperationids('success') + validate_template(res, schema) + + def test_z_validateaddress(self, test_params): + schema = { + 'type': 'object', + 'properties': { + 'isvalid': {'type': 'boolean'}, + 'address': {'type': 'string'}, + 'payingkey': {'type': 'string'}, + 'transmissionkey': {'type': 'string'}, + 'ismine': {'type': 'boolean'} + } + } + rpc = test_params.get('node1').get('rpc') + zaddr = rpc.z_getnewaddress() + res = rpc.z_validateaddress(zaddr) + validate_template(res, schema) diff --git a/qa/pytest_komodo/basic/test_utils.py b/qa/pytest_komodo/basic/test_utils.py new file mode 100644 index 00000000000..da95a296773 --- /dev/null +++ b/qa/pytest_komodo/basic/test_utils.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python3 +# Copyright (c) 2019 SuperNET developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import pytest +from pytest_util import validate_template + + +@pytest.mark.usefixtures("proxy_connection") +class TestUtil: + + def test_createmultisig(self, test_params): + schema = { + 'type': 'object', + 'properties': { + 'address': {'type': 'string'}, + 'redeemScript': {'type': 'string'}, + } + } + rpc = test_params.get('node1').get('rpc') + keys = [test_params.get('node1').get('pubkey'), test_params.get('node2').get('pubkey')] + res = rpc.createmultisig(2, keys) + validate_template(res, schema) + + def test_decodeccopret(self, test_params): + rpc = test_params.get('node1').get('rpc') + schema = { + 'type': 'object', + 'properties': { + 'result': {'type': 'string'}, + 'OpRets': { + 'type': 'array', + 'items': { + 'type': 'object', + 'properties': { + 'eval_code': {'type': 'string'}, + 'function': {'type': 'string'} + } + } + } + } + } + rawhex = rpc.oraclescreate('TEST', 'Orale creation tx for test purpose', 'L') + res = rpc.decoderawtransaction(rawhex.get('hex')) + vouts = res.get('vout') + ccopret = '' + for n in vouts: + if n.get('scriptPubKey').get('type') == 'nulldata': + ccopret = n.get('scriptPubKey').get('hex') + break + res = rpc.decodeccopret(ccopret) + assert res.get('result') == 'success' + validate_template(res, schema) + + def test_estimatefee(self, test_params): + rpc = test_params.get('node1').get('rpc') + res = rpc.estimatefee(6) + assert isinstance(res, int) or isinstance(res, float) + + def test_estimatepriority(self, test_params): + rpc = test_params.get('node1').get('rpc') + res = rpc.estimatepriority(6) + assert isinstance(res, int) or isinstance(res, float) + + def test_invalidate_reconsider_block(self, test_params): + rpc = test_params.get('node1').get('rpc') + res = rpc.getinfo() + block = res.get('blocks') + blockhash = rpc.getblockhash(block) + res = rpc.invalidateblock(blockhash) + assert not res # none response on success + res = rpc.reconsiderblock(blockhash) + assert not res # none response on success + + def test_txnotarizedconfirmed(self, test_params): + rpc = test_params.get('node1').get('rpc') + res = rpc.listunspent() + txid = res[0].get('txid') + res = rpc.txnotarizedconfirmed(txid) + assert isinstance(res.get('result'), bool) + + def test_validateaddress(self, test_params): + schema = { + 'type': 'object', + 'properties': { + 'isvalid': {'type': 'boolean'}, + 'ismine': {'type': 'boolean'}, + 'isscript': {'type': 'boolean'}, + 'iscompressed': {'type': 'boolean'}, + 'account': {'type': 'string'}, + 'pubkey': {'type': 'string'}, + 'address': {'type': 'string'}, + 'scriptPubKey': {'type': 'string'}, + 'segid': {'type': 'integer'} + } + } + rpc = test_params.get('node1').get('rpc') + addr = test_params.get('node1').get('address') + res = rpc.validateaddress(addr) + validate_template(res, schema) + assert addr == res.get('address') + + def test_verifymessage(self, test_params): + rpc = test_params.get('node1').get('rpc') + addr = test_params.get('node1').get('address') + sign = rpc.signmessage(addr, 'test test') + assert isinstance(sign, str) + res = rpc.verifymessage(addr, sign, "test test") + assert isinstance(res, bool) diff --git a/qa/pytest_komodo/basic/test_wallet.py b/qa/pytest_komodo/basic/test_wallet.py new file mode 100644 index 00000000000..2da614cdb2d --- /dev/null +++ b/qa/pytest_komodo/basic/test_wallet.py @@ -0,0 +1,343 @@ +#!/usr/bin/env python3 +# Copyright (c) 2019 SuperNET developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import pytest +import os +import time +from decimal import * +from pytest_util import validate_template, mine_and_waitconfirms + + +@pytest.mark.usefixtures("proxy_connection") +class TestWalletRPC: + + def test_addmultisigadress(self, test_params): + pass + + def test_getbalance(self, test_params): + rpc = test_params.get('node1').get('rpc') + res = rpc.getbalance() + # python-bitcoinrpc Proxy can return value as decimal + assert isinstance(res, float) or isinstance(res, int) or isinstance(res, Decimal) + + def test_getnewaddress(self, test_params): + rpc = test_params.get('node1').get('rpc') + res = rpc.getnewaddress() + assert isinstance(res, str) + + def test_dumpprivkey(self, test_params): + rpc = test_params.get('node1').get('rpc') + addr = rpc.getnewaddress() + res = rpc.dumpprivkey(addr) + assert isinstance(res, str) + + def test_getrawchangeaddress(self, test_params): + rpc = test_params.get('node1').get('rpc') + res = rpc.getrawchangeaddress() + assert isinstance(res, str) + + def test_getreceivedbyaddress(self, test_params): + rpc = test_params.get('node1').get('rpc') + addr = rpc.getnewaddress() + res = rpc.getreceivedbyaddress(addr) + # python-bitcoinrpc Proxy can return value as decimal + assert isinstance(res, float) or isinstance(res, int) or isinstance(res, Decimal) + + def test_gettransaction(self, test_params): + schema = { + 'type': 'object', + 'properties': { + 'amount': {'type': ['integer', 'number']}, + 'rawconfirmations': {'type': 'integer'}, + 'confirmations': {'type': 'integer'}, + 'blockindex': {'type': 'integer'}, + 'blocktime': {'type': 'integer'}, + 'walletconflicts': {'type': 'array'}, + 'time': {'type': 'integer'}, + 'timereceived': {'type': 'integer'}, + 'txid': {'type': 'string'}, + 'blockhash': {'type': 'string'}, + 'hex': {'type': 'string'}, + 'vjoinsplit': { + 'type': 'array', + 'items': {'type': 'object'} + }, + 'details': { + 'type': 'array', + 'properties': { + 'account': {'type': 'string'}, + 'address': {'type': 'string'}, + 'category': {'type': 'string'}, + 'amount': {'type': ['number', 'integer']}, + 'vout': {'type': 'integer'}, + 'size': {'type': 'integer'}, + } + } + } + } + rpc = test_params.get('node1').get('rpc') + res = rpc.listunspent() + txid = res[0].get('txid') + res = rpc.gettransaction(txid) + validate_template(res, schema) + + def test_getunconfirmedbalance(self, test_params): + rpc = test_params.get('node1').get('rpc') + res = rpc.getunconfirmedbalance() + # python-bitcoinrpc Proxy can return value as decimal + assert isinstance(res, float) or isinstance(res, int) or isinstance(res, Decimal) + + def test_getwalletinfo(self, test_params): + schema = { + 'type': 'object', + 'properties': { + 'walletversion': {'type': 'integer'}, + 'balance': {'type': ['number', 'integer']}, + 'unconfirmed_balance': {'type': ['number', 'integer']}, + 'immature_balance': {'type': ['number', 'integer']}, + 'txount': {'type': 'integer'}, + 'keypoololdest': {'type': 'integer'}, + 'keypoolsize': {'type': 'integer'}, + 'paytxfee': {'type': ['number', 'integer']}, + } + } + rpc = test_params.get('node1').get('rpc') + res = rpc.getwalletinfo() + validate_template(res, schema) + + def test_importaddress(self, test_params): + rpc1 = test_params.get('node1').get('rpc') + rpc2 = test_params.get('node2').get('rpc') + addr = rpc2.getnewaddress() + res = rpc1.importaddress(addr) + assert not res # empty response on success + + def test_importprivkey(self, test_params): + rpc = test_params.get('node1').get('rpc') + addr = rpc.getnewaddress() + key = rpc.dumpprivkey(addr) + res = rpc.importprivkey(key) + assert isinstance(res, str) + + def test_keypoolrefill(self, test_params): + rpc = test_params.get('node1').get('rpc') + res = rpc.keypoolrefill(100) + assert not res # empty response on success + + def test_listaddressgroupings(self, test_params): + schema = { + 'type': 'array', + 'items': { + 'type': 'array', + 'items': { + 'type': 'array', + 'items': {'type': ['string', 'integer', 'number']} + } + } + } + rpc = test_params.get('node1').get('rpc') + res = rpc.listaddressgroupings() + validate_template(res, schema) + + def test_list_lockunspent(self, test_params): + schema = { + 'type': 'array', + 'items': { + 'type': 'object', + 'properties': { + 'txid': {'type': 'string'}, + 'vout': {'type': 'integer'} + } + } + } + rpc = test_params.get('node1').get('rpc') + res = rpc.listunspent() + txid = res[0].get('txid') + lock = [{"txid": txid, "vout": 0}] + res = rpc.lockunspent(False, lock) + assert res # returns True on success + res = rpc.listlockunspent() + validate_template(res, schema) + res = rpc.lockunspent(True, lock) + assert res # returns True on success + + def test_listreceivedbyaddress(self, test_params): + schema = { + 'type': 'array', + 'items': { + 'type': 'object', + 'properties': { + 'involvesWatchonly': {'type': 'boolean'}, + 'address': {'type': 'string'}, + 'account': {'type': 'string'}, + 'amount': {'type': ['integer', 'number']}, + 'rawconfirmations': {'type': 'integer'}, + 'confirmations': {'type': 'integer'}, + 'txids': { + 'type': 'array', + 'items': {'type': 'string'} + } + } + } + } + rpc = test_params.get('node1').get('rpc') + res = rpc.listreceivedbyaddress() + validate_template(res, schema) + + def test_listsinceblock(self, test_params): + schema = { + 'type': 'object', + 'properties': { + 'lastblock': {'type': 'string'}, + 'transactions': { + 'type': 'array', + 'items': { + 'type': 'object', + 'properties': { + 'account': {'type': 'string'}, + 'address': {'type': 'string'}, + 'category': {'type': 'string'}, + 'blockhash': {'type': 'string'}, + 'txid': {'type': 'string'}, + 'vjoinsplit': {'type': 'array'}, + 'walletconflicts': {'type': 'array'}, + 'amount': {'type': ['integer', 'number']}, + 'vout': {'type': 'integer'}, + 'rawconfirmations': {'type': 'integer'}, + 'confirmations': {'type': 'integer'}, + 'blockindex': {'type': 'integer'}, + 'blocktime': {'type': 'integer'}, + 'expiryheight': {'type': 'integer'}, + 'time': {'type': 'integer'}, + 'timereceived': {'type': 'integer'}, + 'size': {'type': 'integer'}, + 'to': {'type': 'string'}, + 'comment': {'type': 'string'} + } + } + } + } + } + rpc = test_params.get('node1').get('rpc') + blockhash = rpc.getbestblockhash() + res = rpc.listsinceblock(blockhash, 1) + validate_template(res, schema) + + def test_listtransactions(self, test_params): + schema = { + 'type': 'array', + 'items': { + 'type': 'object', + 'properties': { + 'account': {'type': 'string'}, + 'address': {'type': 'string'}, + 'category': {'type': 'string'}, + 'blockhash': {'type': 'string'}, + 'txid': {'type': 'string'}, + 'vjoinsplit': {'type': 'array'}, + 'walletconflicts': {'type': 'array'}, + 'amount': {'type': ['integer', 'number']}, + 'vout': {'type': 'integer'}, + 'rawconfirmations': {'type': 'integer'}, + 'confirmations': {'type': 'integer'}, + 'blockindex': {'type': 'integer'}, + 'fee': {'type': ['integer', 'number']}, + 'time': {'type': 'integer'}, + 'timereceived': {'type': 'integer'}, + 'size': {'type': 'integer'}, + 'comment': {'type': 'string'}, + 'otheraccount': {'type': 'string'} + } + } + } + rpc = test_params.get('node1').get('rpc') + res = rpc.listtransactions() + validate_template(res, schema) + + def test_listunspent(self, test_params): + schema = { + 'type': 'array', + 'items': { + 'type': 'object', + 'properties': { + 'txid': {'type': 'string'}, + 'address': {'type': 'string'}, + 'scriptPubKey': {'type': 'string'}, + 'generated': {'type': 'boolean'}, + 'spendable': {'type': 'boolean'}, + 'vout': {'type': 'integer'}, + 'confirmations': {'type': 'integer'}, + 'rawconfirmations': {'type': 'integer'}, + 'amount': {'type': ['integer', 'number']}, + 'interest': {'type': ['integer', 'number']} + } + } + } + rpc = test_params.get('node1').get('rpc') + res = rpc.listunspent() + validate_template(res, schema) + + def test_resendwallettransactions(self, test_params): + schema = { + 'type': 'array', + 'itmes': {'type': 'string'} + } + rpc = test_params.get('node1').get('rpc') + res = rpc.resendwallettransactions() + validate_template(res, schema) + + def test_settxfee(self, test_params): + txfee = 0.00001 + rpc = test_params.get('node1').get('rpc') + res = rpc.settxfee(txfee) + assert res # returns True on success + + def test_signmessage(self, test_params): + message = "my test message" + rpc = test_params.get('node1').get('rpc') + addr = rpc.getnewaddress() + sign = rpc.signmessage(addr, message) + assert isinstance(sign, str) + res = rpc.verifymessage(addr, sign, message) + assert res # returns True on success + + def test_sendtoaddress(self, test_params): + rpc = test_params.get('node1').get('rpc') + addr = rpc.getnewaddress() + # python float() is double precision floating point number, + # where sendmany expects regural float (8 digits) value + # "{0:.8f}".format(value)) returns number string with 8 digit precision and float() corrects the type + amount = float("{0:.8f}".format(rpc.listunspent()[-1].get('amount') / 10)) + txid = rpc.sendtoaddress(addr, amount) + assert isinstance(txid, str) + # wait tx to be confirmed + mine_and_waitconfirms(txid, rpc) + + def test_sendmany(self, test_params): + rpc1 = test_params.get('node1').get('rpc') + rpc2 = test_params.get('node2').get('rpc') + address1 = rpc1.getnewaddress() + address2 = rpc2.getnewaddress() + # clarification in test_sendtoaddress above + amount = float("{0:.8f}".format(rpc1.listunspent()[-1].get('amount') / 10)) # float("{0:.8f}".format(amount2)) + send = {address1: amount, address2: amount} + txid = rpc1.sendmany("", send) + assert isinstance(txid, str) + # wait tx to be confirmed + mine_and_waitconfirms(txid, rpc1) + + def test_setupkey(self, test_params): + schema = { + 'type': 'object', + 'properties': { + 'address': {'type': 'string'}, + 'pubkey': {'type': 'string'}, + 'ismine': {'type': 'boolean'} + } + } + rpc = test_params.get('node1').get('rpc') + pubkey = test_params.get('node1').get('pubkey') + res = rpc.setpubkey(pubkey) + validate_template(res, schema) diff --git a/qa/rpc-tests/cc_pytest/test_channels.py b/qa/pytest_komodo/cc_modules/test_channels.py similarity index 73% rename from qa/rpc-tests/cc_pytest/test_channels.py rename to qa/pytest_komodo/cc_modules/test_channels.py index 28e8a50b5ac..fd72b2139f0 100644 --- a/qa/rpc-tests/cc_pytest/test_channels.py +++ b/qa/pytest_komodo/cc_modules/test_channels.py @@ -4,27 +4,23 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. import pytest -import json import time -from util import assert_success, assert_error, check_if_mined, send_and_mine, rpc_connect, wait_some_blocks, generate_random_string +from util import assert_success, assert_error, check_if_mined, send_and_mine,\ + rpc_connect, wait_some_blocks, generate_random_string, komodo_teardown -def test_channels(): +@pytest.mark.usefixtures("proxy_connection") +def test_channels(test_params): # test params inits - with open('test_config.json', 'r') as f: - params_dict = json.load(f) + rpc = test_params.get('node1').get('rpc') + rpc1 = test_params.get('node2').get('rpc') - node1_params = params_dict["node1"] - node2_params = params_dict["node2"] + pubkey = test_params.get('node1').get('pubkey') + pubkey1 = test_params.get('node2').get('pubkey') - rpc = rpc_connect(node1_params["rpc_user"], node1_params["rpc_password"], node1_params["rpc_ip"], node1_params["rpc_port"]) - rpc1 = rpc_connect(node2_params["rpc_user"], node2_params["rpc_password"], node2_params["rpc_ip"], node2_params["rpc_port"]) - pubkey = node1_params["pubkey"] - pubkey1 = node2_params["pubkey"] - - is_fresh_chain = params_dict["is_fresh_chain"] + is_fresh_chain = test_params.get("is_fresh_chain") """!!! for testing needed test daemon which built with custom flag export CONFIGURE_FLAGS='CPPFLAGS=-DTESTMODE' @@ -119,10 +115,11 @@ def test_channels(): assert result == 200000 result = rpc1.validateaddress(raw_transaction["vout"][3]["scriptPubKey"]["addresses"][0])["ismine"] - assert result == True + assert result # have to check that second node have coins to cover txfee at least rpc.sendtoaddress(rpc1.getnewaddress(), 1) + time.sleep(10) # to ensure transactions are in different blocks rpc.sendtoaddress(rpc1.getnewaddress(), 1) wait_some_blocks(rpc, 2) result = rpc1.getbalance() @@ -166,7 +163,7 @@ def test_channels(): assert result == 700000 result = rpc.validateaddress(raw_transaction["vout"][2]["scriptPubKey"]["addresses"][0])["ismine"] - assert result == True + assert result # creating and draining channel (10 payment by 100000 satoshies in total to fit full capacity) new_channel_hex1 = rpc.channelsopen(pubkey1, "10", "100000") @@ -193,40 +190,40 @@ def test_channels(): assert_error(result) # TODO: fixme -# # creating new channel to test the case when node B initiate payment when node A revealed secret in offline -# # 10 payments, 100000 sat denomination channel opening with second node pubkey -# new_channel_hex2 = rpc.channelsopen(pubkey1, "10", "100000") -# assert_success(new_channel_hex) -# channel2_txid = send_and_mine(new_channel_hex2["hex"], rpc) -# assert channel2_txid, "got channel txid" - -# wait_some_blocks(rpc, 2) - -# # disconnecting first node from network -# rpc.setban("127.0.0.0/24", "add") -# assert rpc.getinfo()["connections"] == 0 -# time.sleep(5) -# assert rpc1.getinfo()["connections"] == 0 - -# # sending one payment to mempool to reveal the secret but not mine it -# payment_hex = rpc.channelspayment(channel2_txid, "100000") -# result = rpc.sendrawtransaction(payment_hex["hex"]) -# assert result, "got payment txid" - -# secret = rpc.channelsinfo(channel2_txid)["Transactions"][1]["Secret"] -# assert secret, "Secret revealed" - -# # secret shouldn't be available for node B -# secret_not_revealed = None -# try: -# rpc1.channelsinfo(channel2_txid)["Transactions"][1]["Secret"] -# except Exception: -# secret_not_revealed = True -# assert secret_not_revealed == True - -# # trying to initiate payment from second node with revealed secret -# assert rpc1.getinfo()["connections"] == 0 -# dc_payment_hex = rpc1.channelspayment(channel2_txid, "100000", secret) -# assert_success(dc_payment_hex) -# result = rpc1.sendrawtransaction(dc_payment_hex["hex"]) -# assert result, "got channelspayment transaction id" +# +# # creating new channel to test the case when node B initiate payment when node A revealed secret in offline +# # 10 payments, 100000 sat denomination channel opening with second node pubkey +# new_channel_hex2 = rpc.channelsopen(pubkey1, "10", "100000") +# assert_success(new_channel_hex) +# channel2_txid = send_and_mine(new_channel_hex2["hex"], rpc) +# assert channel2_txid, "got channel txid" +# +# wait_some_blocks(rpc, 2) +# +# # disconnecting first node from network +# rpc.setban("127.0.0.0/24", "add") +# assert rpc.getinfo()["connections"] == 0 +# assert rpc1.getinfo()["connections"] == 0 +# +# # sending one payment to mempool to reveal the secret but not mine it +# payment_hex = rpc.channelspayment(channel2_txid, "100000") +# result = rpc.sendrawtransaction(payment_hex["hex"]) +# assert result, "got payment txid" +# +# secret = rpc.channelsinfo(channel2_txid)["Transactions"][1]["Secret"] +# assert secret, "Secret revealed" +# +# # secret shouldn't be available for node B +# secret_not_revealed = None +# try: +# rpc1.channelsinfo(channel2_txid)["Transactions"][1]["Secret"] +# except Exception: +# secret_not_revealed +# assert secret_not_revealed +# +# # trying to initiate payment from second node with revealed secret +# assert rpc1.getinfo()["connections"] == 0 +# dc_payment_hex = rpc1.channelspayment(channel2_txid, "100000", secret) +# assert_success(dc_payment_hex) +# result = rpc1.sendrawtransaction(dc_payment_hex["hex"]) +# assert result, "got channelspayment transaction id" diff --git a/qa/rpc-tests/cc_pytest/test_dice.py b/qa/pytest_komodo/cc_modules/test_dice.py similarity index 89% rename from qa/rpc-tests/cc_pytest/test_dice.py rename to qa/pytest_komodo/cc_modules/test_dice.py index b7f48bd3ff0..d031bee33ea 100644 --- a/qa/rpc-tests/cc_pytest/test_dice.py +++ b/qa/pytest_komodo/cc_modules/test_dice.py @@ -6,24 +6,22 @@ import pytest import json -from util import assert_success, assert_error, check_if_mined, send_and_mine, rpc_connect, wait_some_blocks, generate_random_string +from util import assert_success, assert_error, check_if_mined, send_and_mine,\ + rpc_connect, wait_some_blocks, generate_random_string, komodo_teardown -def test_dice(): +@pytest.mark.usefixtures("proxy_connection") +def test_dice(test_params): # test params inits - with open('test_config.json', 'r') as f: - params_dict = json.load(f) + rpc = test_params.get('node1').get('rpc') + rpc1 = test_params.get('node2').get('rpc') - node1_params = params_dict["node1"] - node2_params = params_dict["node2"] + pubkey = test_params.get('node1').get('pubkey') + pubkey1 = test_params.get('node2').get('pubkey') - rpc = rpc_connect(node1_params["rpc_user"], node1_params["rpc_password"], node1_params["rpc_ip"], node1_params["rpc_port"]) - rpc1 = rpc_connect(node2_params["rpc_user"], node2_params["rpc_password"], node2_params["rpc_ip"], node2_params["rpc_port"]) - pubkey = node1_params["pubkey"] - pubkey1 = node2_params["pubkey"] + is_fresh_chain = test_params.get("is_fresh_chain") - is_fresh_chain = params_dict["is_fresh_chain"] # second node should have some balance to place bets result = rpc1.getbalance() @@ -191,4 +189,4 @@ def test_dice(): # # funding balance should increase if player loss, decrease if player won # fundbalanceguess = funding + losscounter - wincounter * 2 # fundinfoactual = rpc.diceinfo(diceid) - # assert_equal(round(fundbalanceguess),round(float(fundinfoactual['funding']))) \ No newline at end of file + # assert_equal(round(fundbalanceguess),round(float(fundinfoactual['funding']))) diff --git a/qa/rpc-tests/cc_pytest/test_faucet.py b/qa/pytest_komodo/cc_modules/test_faucet.py similarity index 83% rename from qa/rpc-tests/cc_pytest/test_faucet.py rename to qa/pytest_komodo/cc_modules/test_faucet.py index a0dde45a6d4..137c9848ff3 100644 --- a/qa/rpc-tests/cc_pytest/test_faucet.py +++ b/qa/pytest_komodo/cc_modules/test_faucet.py @@ -8,22 +8,18 @@ from util import assert_success, assert_error, check_if_mined, send_and_mine, rpc_connect -@pytest.mark.first -def test_faucet(): - # test params inits - with open('test_config.json', 'r') as f: - params_dict = json.load(f) +@pytest.mark.usefixtures("proxy_connection") +def test_faucet(test_params): - node1_params = params_dict["node1"] - node2_params = params_dict["node2"] + # test params inits + rpc = test_params.get('node1').get('rpc') + rpc1 = test_params.get('node2').get('rpc') - rpc = rpc_connect(node1_params["rpc_user"], node1_params["rpc_password"], node1_params["rpc_ip"], node1_params["rpc_port"]) - rpc1 = rpc_connect(node2_params["rpc_user"], node2_params["rpc_password"], node2_params["rpc_ip"], node2_params["rpc_port"]) - pubkey = node1_params["pubkey"] - pubkey1 = node2_params["pubkey"] + pubkey = test_params.get('node1').get('pubkey') + pubkey1 = test_params.get('node2').get('pubkey') - is_fresh_chain = params_dict["is_fresh_chain"] + is_fresh_chain = test_params.get("is_fresh_chain") # faucet got only one entity per chain @@ -107,4 +103,4 @@ def test_faucet(): #assert txid, "transaction broadcasted" #balance2 = rpc1.getwalletinfo()['balance'] - #assert balance2 > balance1 \ No newline at end of file + #assert balance2 > balance1 diff --git a/qa/rpc-tests/cc_pytest/test_heir.py b/qa/pytest_komodo/cc_modules/test_heir.py similarity index 86% rename from qa/rpc-tests/cc_pytest/test_heir.py rename to qa/pytest_komodo/cc_modules/test_heir.py index 4b163e7b367..dd384704bb4 100644 --- a/qa/rpc-tests/cc_pytest/test_heir.py +++ b/qa/pytest_komodo/cc_modules/test_heir.py @@ -7,23 +7,21 @@ import time import json -from util import assert_success, assert_error, check_if_mined, send_and_mine, rpc_connect, wait_some_blocks +from util import assert_success, assert_error, check_if_mined, send_and_mine, \ + rpc_connect, wait_some_blocks, komodo_teardown -def test_heir(): +@pytest.mark.usefixtures("proxy_connection") +def test_heir(test_params): # test params inits - with open('test_config.json', 'r') as f: - params_dict = json.load(f) + rpc = test_params.get('node1').get('rpc') + rpc1 = test_params.get('node2').get('rpc') - node1_params = params_dict["node1"] - node2_params = params_dict["node2"] + pubkey = test_params.get('node1').get('pubkey') + pubkey1 = test_params.get('node2').get('pubkey') - rpc = rpc_connect(node1_params["rpc_user"], node1_params["rpc_password"], node1_params["rpc_ip"], node1_params["rpc_port"]) - rpc1 = rpc_connect(node2_params["rpc_user"], node2_params["rpc_password"], node2_params["rpc_ip"], node2_params["rpc_port"]) - pubkey = node1_params["pubkey"] - pubkey1 = node2_params["pubkey"] - is_fresh_chain = params_dict["is_fresh_chain"] + is_fresh_chain = test_params.get("is_fresh_chain") result = rpc.heiraddress('') assert_success(result) @@ -68,7 +66,7 @@ def test_heir(): assert result["type"] == "coins" assert result["InactivityTimeSetting"] == "10" # TODO: we have non insta blocks now so should set inactivity time more than blocktime to proper test it - #assert result["IsHeirSpendingAllowed"] == "false" + # assert result["IsHeirSpendingAllowed"] == "false" # waiting for 11 seconds to be sure that needed time passed for heir claiming time.sleep(11) @@ -81,6 +79,7 @@ def test_heir(): second_node_balance = rpc1.getbalance() if second_node_balance < 0.1: rpc.sendtoaddress(rpc1.getnewaddress(), 1) + time.sleep(10) # to ensure transactions are in different blocks rpc.sendtoaddress(rpc1.getnewaddress(), 1) wait_some_blocks(rpc, 2) assert second_node_balance > 0.1 @@ -94,8 +93,8 @@ def test_heir(): # balance of second node after heirclaim should increase for 1000 coins - txfees # + get one block reward when broadcasted heir_claim_txid # TODO: very bad test with non-clearly hardcoded blockreward - needs to be changed - #result = round(rpc1.getbalance()) - round(second_node_balance) - #assert result > 100999 + # result = round(rpc1.getbalance()) - round(second_node_balance) + # assert result > 100999 # no more funds should be available for claiming result = rpc.heirinfo(heir_fund_txid) @@ -127,7 +126,7 @@ def test_heir(): assert result["type"] == "tokens" assert result["InactivityTimeSetting"] == "10" # TODO: we have non insta blocks now so should set inactivity time more than blocktime to proper test it - #assert result["IsHeirSpendingAllowed"] == "false" + # assert result["IsHeirSpendingAllowed"] == "false" # waiting for 11 seconds to be sure that needed time passed for heir claiming time.sleep(11) @@ -139,7 +138,7 @@ def test_heir(): # let's claim whole heir sum from second node result = rpc1.heirclaim("100000000", token_heir_txid) assert_success(result) - + heir_tokens_claim_txid = send_and_mine(result["hex"], rpc1) assert heir_tokens_claim_txid, "got claim txid" diff --git a/qa/rpc-tests/cc_pytest/test_oracles.py b/qa/pytest_komodo/cc_modules/test_oracles.py similarity index 91% rename from qa/rpc-tests/cc_pytest/test_oracles.py rename to qa/pytest_komodo/cc_modules/test_oracles.py index e7a7ae3bc4d..df262e3c97b 100644 --- a/qa/rpc-tests/cc_pytest/test_oracles.py +++ b/qa/pytest_komodo/cc_modules/test_oracles.py @@ -4,26 +4,23 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. import pytest -import json +import os +import time +from util import assert_success, assert_error, check_if_mined,\ + send_and_mine, rpc_connect, wait_some_blocks, generate_random_string, komodo_teardown -from util import assert_success, assert_error, check_if_mined, send_and_mine, rpc_connect, wait_some_blocks, generate_random_string - -def test_oracles(): +@pytest.mark.usefixtures("proxy_connection") +def test_oracles(test_params): # test params inits - with open('test_config.json', 'r') as f: - params_dict = json.load(f) - - node1_params = params_dict["node1"] - node2_params = params_dict["node2"] + rpc = test_params.get('node1').get('rpc') + rpc1 = test_params.get('node2').get('rpc') - rpc = rpc_connect(node1_params["rpc_user"], node1_params["rpc_password"], node1_params["rpc_ip"], node1_params["rpc_port"]) - rpc1 = rpc_connect(node2_params["rpc_user"], node2_params["rpc_password"], node2_params["rpc_ip"], node2_params["rpc_port"]) - pubkey = node1_params["pubkey"] - pubkey1 = node2_params["pubkey"] + pubkey = test_params.get('node1').get('pubkey') + pubkey1 = test_params.get('node2').get('pubkey') - is_fresh_chain = params_dict["is_fresh_chain"] + is_fresh_chain = test_params.get("is_fresh_chain") result = rpc.oraclesaddress() assert_success(result) @@ -65,14 +62,15 @@ def test_oracles(): # valid creating oracles of different types # using such naming to re-use it for data publishing / reading (e.g. oracle_s for s type) print(len(rpc.listunspent())) + # enable mining valid_formats = ["s", "S", "d", "D", "c", "C", "t", "T", "i", "I", "l", "L", "h", "Ihh"] for f in valid_formats: result = rpc.oraclescreate("Test_" + f, "Test_" + f, f) assert_success(result) - globals()["oracle_{}".format(f)] = rpc.sendrawtransaction(result['hex']) - - wait_some_blocks(rpc, 1) + # globals()["oracle_{}".format(f)] = rpc.sendrawtransaction(result['hex']) + globals()["oracle_{}".format(f)] = send_and_mine(result['hex'], rpc) + list_fund_txid = [] for f in valid_formats: # trying to register with negative datafee result = rpc.oraclesregister(globals()["oracle_{}".format(f)], "-100") @@ -94,15 +92,26 @@ def test_oracles(): result = rpc.oraclesfund(globals()["oracle_{}".format(f)]) assert_success(result) fund_txid = rpc.sendrawtransaction(result["hex"]) + list_fund_txid.append(fund_txid) assert fund_txid, "got txid" - wait_some_blocks(rpc, 1) + wait_some_blocks(rpc, 2) + + for t in list_fund_txid: + c = 0 + print("Waiting confiramtions for oraclesfund") + while c < 2: + try: + c = rpc.getrawtransaction(t, 1)['confirmations'] + except KeyError: + time.sleep(29) + print("Oracles fund confirmed \n", t) for f in valid_formats: # trying to register valid (funded) - result = rpc.oraclesregister(globals()["oracle_{}".format(f)], "10000") - print(f) + result = rpc.oraclesregister(globals()["oracle_{}".format(f)], "100000") assert_success(result) + print("Registering ", f) register_txid = rpc.sendrawtransaction(result["hex"]) assert register_txid, "got txid" diff --git a/qa/rpc-tests/cc_pytest/test_rewards.py b/qa/pytest_komodo/cc_modules/test_rewards.py similarity index 86% rename from qa/rpc-tests/cc_pytest/test_rewards.py rename to qa/pytest_komodo/cc_modules/test_rewards.py index 82d2cb22d7b..3c2759852bc 100644 --- a/qa/rpc-tests/cc_pytest/test_rewards.py +++ b/qa/pytest_komodo/cc_modules/test_rewards.py @@ -5,25 +5,24 @@ import pytest import json +from util import assert_success, assert_error, check_if_mined, send_and_mine,\ + rpc_connect, wait_some_blocks, generate_random_string, komodo_teardown -from util import assert_success, assert_error, check_if_mined, send_and_mine, rpc_connect, wait_some_blocks, generate_random_string - -def test_faucet(): +@pytest.mark.usefixtures("proxy_connection") +def test_rewards(test_params): # test params inits - with open('test_config.json', 'r') as f: - params_dict = json.load(f) + rpc = test_params.get('node1').get('rpc') + rpc1 = test_params.get('node2').get('rpc') - node1_params = params_dict["node1"] - node2_params = params_dict["node2"] + pubkey = test_params.get('node1').get('pubkey') + pubkey1 = test_params.get('node2').get('pubkey') - rpc = rpc_connect(node1_params["rpc_user"], node1_params["rpc_password"], node1_params["rpc_ip"], node1_params["rpc_port"]) - rpc1 = rpc_connect(node2_params["rpc_user"], node2_params["rpc_password"], node2_params["rpc_ip"], node2_params["rpc_port"]) - pubkey = node1_params["pubkey"] - pubkey1 = node2_params["pubkey"] + is_fresh_chain = test_params.get("is_fresh_chain") - is_fresh_chain = params_dict["is_fresh_chain"] + global proxy + proxy = [rpc, rpc1] result = rpc.rewardsaddress() for x in result.keys(): @@ -132,4 +131,4 @@ def test_faucet(): # will not unlock since reward amount is less than tx fee result = rpc.rewardsunlock(plan_name, fundingtxid, locktxid) - assert_error(result) \ No newline at end of file + assert_error(result) diff --git a/qa/rpc-tests/cc_pytest/test_token.py b/qa/pytest_komodo/cc_modules/test_token.py similarity index 91% rename from qa/rpc-tests/cc_pytest/test_token.py rename to qa/pytest_komodo/cc_modules/test_token.py index f92191aeeaa..a1642cc306c 100644 --- a/qa/rpc-tests/cc_pytest/test_token.py +++ b/qa/pytest_komodo/cc_modules/test_token.py @@ -4,26 +4,23 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. import pytest -import json +import time -from util import assert_success, assert_error, check_if_mined, send_and_mine, rpc_connect, wait_some_blocks +from util import assert_success, assert_error, check_if_mined,\ + send_and_mine, rpc_connect, wait_some_blocks, komodo_teardown -def test_faucet(): +@pytest.mark.usefixtures("proxy_connection") +def test_token(test_params): # test params inits - with open('test_config.json', 'r') as f: - params_dict = json.load(f) + rpc = test_params.get('node1').get('rpc') + rpc1 = test_params.get('node2').get('rpc') - node1_params = params_dict["node1"] - node2_params = params_dict["node2"] + pubkey = test_params.get('node1').get('pubkey') + pubkey1 = test_params.get('node2').get('pubkey') - rpc = rpc_connect(node1_params["rpc_user"], node1_params["rpc_password"], node1_params["rpc_ip"], node1_params["rpc_port"]) - rpc1 = rpc_connect(node2_params["rpc_user"], node2_params["rpc_password"], node2_params["rpc_ip"], node2_params["rpc_port"]) - pubkey = node1_params["pubkey"] - pubkey1 = node2_params["pubkey"] - - is_fresh_chain = params_dict["is_fresh_chain"] + is_fresh_chain = test_params.get("is_fresh_chain") result = rpc.tokenaddress() assert_success(result) @@ -158,6 +155,7 @@ def test_faucet(): # from other node (ensuring that second node have enough balance to cover txfee # to get the actual error - not "not enough balance" one rpc.sendtoaddress(rpc1.getnewaddress(), 1) + time.sleep(10) # to ensure transactions are in different blocks rpc.sendtoaddress(rpc1.getnewaddress(), 1) wait_some_blocks(rpc, 2) result = rpc1.getbalance() diff --git a/qa/rpc-tests/cc_pytest/util.py b/qa/pytest_komodo/cc_modules/util.py similarity index 70% rename from qa/rpc-tests/cc_pytest/util.py rename to qa/pytest_komodo/cc_modules/util.py index dcca087e141..eb37fc641dc 100644 --- a/qa/rpc-tests/cc_pytest/util.py +++ b/qa/pytest_komodo/cc_modules/util.py @@ -3,7 +3,10 @@ import sys from random import choice from string import ascii_uppercase -from slickrpc import Proxy +try: + from slickrpc import Proxy +except ImportError: + from bitcoinrpc.authproxy import AuthServiceProxy as Proxy def assert_success(result): @@ -44,7 +47,7 @@ def send_and_mine(tx_hex, rpc_connection): def rpc_connect(rpc_user, rpc_password, ip, port): try: - rpc_connection = Proxy("http://%s:%s@%s:%d"%(rpc_user, rpc_password, ip, port)) + rpc_connection = Proxy("http://%s:%s@%s:%d" % (rpc_user, rpc_password, ip, port)) except Exception: raise Exception("Connection error! Probably no daemon on selected port.") return rpc_connection @@ -65,3 +68,20 @@ def wait_some_blocks(rpc_connection, blocks_to_wait): def generate_random_string(length): random_string = ''.join(choice(ascii_uppercase) for i in range(length)) return random_string + + +def komodo_teardown(*proxy_instances): + for instance in proxy_instances: + if type(instance) is list: + for iteratable in instance: + try: + isinstance(iteratable, Proxy) + iteratable.stop() + except Exception as e: + raise TypeError("Not a Proxy object, error: " + str(e)) + else: + try: + isinstance(instance, Proxy) + instance.stop() + except Exception as e: + raise TypeError("Not a Proxy object, error: " + str(e)) diff --git a/qa/pytest_komodo/chainconfig.json b/qa/pytest_komodo/chainconfig.json new file mode 100644 index 00000000000..32775a55379 --- /dev/null +++ b/qa/pytest_komodo/chainconfig.json @@ -0,0 +1,14 @@ +{ + "TONYCI": { + "coin": "TONYCI", + "rpc_user": "test", + "rpcpassword": "test", + "rpcallowip": "0.0.0.0/0", + "rpcport": 7000, + "port": 6000, + "rpcbind": "0.0.0.0", + "ac_reward": "100000000000", + "ac_supply": "10000000000", + "ac_cc": "2" + } +} \ No newline at end of file diff --git a/qa/pytest_komodo/chainstart.py b/qa/pytest_komodo/chainstart.py new file mode 100644 index 00000000000..fbdf2c8d95b --- /dev/null +++ b/qa/pytest_komodo/chainstart.py @@ -0,0 +1,136 @@ +import os +import json +import time +import subprocess +import wget +import tarfile +from basic.pytest_util import create_proxy, validate_proxy, enable_mining + + +def load_env_config(): + tp = {} # test env parameters + if os.name == 'posix': + envconfig = './envconfig.json' + else: + envconfig = 'envconfig.json' + if os.environ['CHAIN']: + tp.update({'clients_to_start': int(os.environ['CLIENTS'])}) + tp.update({'is_bootstrap_needed': os.environ['IS_BOOTSTRAP_NEEDED']}) + tp.update({'bootstrap_url': os.environ['BOOTSTRAP_URL']}) + tp.update({'chain_start_mode': os.environ['CHAIN_MODE']}) + tp.update({'ac_name': os.environ['CHAIN']}) + test_wif_list = [] # preset empty params lists + test_addr_list = [] + test_pubkey_list = [] + for i in range(tp.get('clients_to_start')): + test_wif_list.append(os.environ["TEST_WIF" + str(i)]) + test_addr_list.append(os.environ["TEST_ADDY" + str(i)]) + test_pubkey_list.append(os.environ["TEST_PUBKEY" + str(i)]) + tp.update({'test_wif': test_wif_list}) + tp.update({'test_address': test_addr_list}) + tp.update({'test_pubkey': test_pubkey_list}) + elif os.path.isfile(envconfig) and not os.environ['CHAIN']: + with open(envconfig, 'r') as f: + tp = json.load(f) + else: + raise EnvironmentError("\nNo test env configuration provided") + return tp + + +def load_ac_params(asset, chain_mode='default'): + if os.name == 'posix': + chainconfig = './chainconfig.json' + binary_path = '../../src/komodod' + else: + chainconfig = (os.getcwd() + '\\chainconfig.json') + binary_path = (os.getcwd() + '\\..\\..\\src\\komodod.exe') + if os.path.isfile(chainconfig): + with open(chainconfig, 'r') as f: + jsonparams = json.load(f) + ac = jsonparams.get(asset) # asset chain parameters + ac.update({'binary_path': binary_path}) + if chain_mode == 'REGTEST': + ac.update({'daemon_params': ['-daemon', '-whitelist=127.0.0.1', '-regtest']}) + else: + ac.update({'daemon_params': ['-daemon', '-whitelist=127.0.0.1']}) + else: + raise EnvironmentError("\nNo asset chains configuration provided") + return ac + + +# TODO: add coins file compatibility with create_configs func +def create_configs(asset, node=0): + if os.name == 'posix': + confpath = ('./node_' + str(node) + '/' + asset + '.conf') + else: + confpath = (os.getcwd() + '\\node_' + str(node) + '\\' + asset + '.conf') + if not os.path.isfile(confpath): + os.mkdir('node_' + str(node)) + open(confpath, 'a').close() + with open(confpath, 'a') as conf: + conf.write("rpcuser=test\n") + conf.write("rpcpassword=test\n") + conf.write('rpcport=' + str(7000 + node) + '\n') + conf.write("rpcbind=0.0.0.0\n") + conf.write("rpcallowip=0.0.0.0/0\n") + + +def main(): + env_params = load_env_config() + clients_to_start = env_params.get('clients_to_start') + aschain = env_params.get('ac_name') + for node in range(clients_to_start): # prepare config folders + create_configs(aschain, node) + if env_params.get('is_bootstrap_needed'): # bootstrap chains + if not os.path.isfile('bootstrap.tar.gz'): + wget.download(env_params.get('bootstrap_url'), "bootstrap.tar.gz") + tf = tarfile.open("bootstrap.tar.gz") + for i in range(clients_to_start): + tf.extractall("node_" + str(i)) + mode = env_params.get('chain_start_mode') + ac_params = load_ac_params(aschain, mode) + for i in range(clients_to_start): # start daemons + if os.name == 'posix': + confpath = (os.getcwd() + '/node_' + str(i) + '/' + aschain + '.conf') + datapath = (os.getcwd() + '/node_' + str(i)) + else: + confpath = (os.getcwd() + '\\node_' + str(i) + '\\' + aschain + '.conf') + datapath = (os.getcwd() + '\\node_' + str(i)) + cl_args = [ac_params.get('binary_path'), + '-ac_name=' + aschain, + '-conf=' + confpath, + '-datadir=' + datapath, + '-pubkey=' + env_params.get('test_pubkey')[i], + ] + if i == 0: + for key in ac_params.keys(): + cl_args.append('-' + key + '=' + str(ac_params.get(key))) + else: + cl_args.append('-addnode=127.0.0.1:' + str(ac_params.get('port'))) + for key in ac_params.keys(): + if isinstance(ac_params.get(key), int): + data = ac_params.get(key) + 1 + cl_args.append('-' + key + '=' + str(data)) + else: + cl_args.append('-' + key + '=' + str(ac_params.get(key))) + cl_args.extend(ac_params.get('daemon_params')) + print(cl_args) + if os.name == "posix": + subprocess.call(cl_args) + else: + subprocess.Popen(cl_args, shell=False, stderr=subprocess.PIPE, stdout=subprocess.PIPE) + time.sleep(5) + for i in range(clients_to_start): + node_params = { + 'rpc_user': 'test', + 'rpc_password': 'test', + 'rpc_ip': '127.0.0.1', + 'rpc_port': 7000 + i + } + rpc_p = create_proxy(node_params) + enable_mining(rpc_p) + validate_proxy(env_params, rpc_p, i) + + +if __name__ == '__main__': + main() diff --git a/qa/pytest_komodo/ci_setup.sh b/qa/pytest_komodo/ci_setup.sh new file mode 100755 index 00000000000..0fb5807a960 --- /dev/null +++ b/qa/pytest_komodo/ci_setup.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +# chains start script params +export CLIENTS=2 +export CHAIN="TONYCI" +export TEST_ADDY0="RPWhA4f4ZTZxNi5K36bcwsWdVjSVDSjUnd" +export TEST_WIF0="UpcQympViQpLmv1WzMwszKPrmKUa28zsv8pdLCMgNMXDFBBBKxCN" +export TEST_PUBKEY0="02f0ec2d3da51b09e4fc8d9ba334c275b02b3ab6f22ce7be0ea5059cbccbd1b8c7" +export TEST_ADDY1="RHoTHYiHD8TU4k9rbY4Aoj3ztxUARMJikH" +export TEST_WIF1="UwmmwgfXwZ673brawUarPzbtiqjsCPWnG311ZRAL4iUCZLBLYeDu" +export TEST_PUBKEY1="0285f68aec0e2f8b5e817d71a2a20a1fda74ea9943c752a13136a3a30fa49c0149" +export CHAIN_MODE="REGULAR" +export IS_BOOTSTRAP_NEEDED="True" +export BOOTSTRAP_URL="https://sirseven.me/share/bootstrap.tar.gz" + +# starting the chains +python3 chainstart.py + +# starting the tests +python3 -m pytest $@ -s -vv diff --git a/qa/pytest_komodo/conftest.py b/qa/pytest_komodo/conftest.py new file mode 100644 index 00000000000..e8b4b9263a2 --- /dev/null +++ b/qa/pytest_komodo/conftest.py @@ -0,0 +1,49 @@ +import pytest +import json +import os +import time +# Using different proxy to bypass libcurl issues on Windows +if os.name == 'posix': + from slickrpc import Proxy +else: + from bitcoinrpc.authproxy import AuthServiceProxy as Proxy + + +@pytest.fixture(scope='session') +def proxy_connection(): + proxy_connected = [] + + def _proxy_connection(node_params_dictionary): + try: + proxy = Proxy("http://%s:%s@%s:%d" % (node_params_dictionary["rpc_user"], + node_params_dictionary["rpc_password"], + node_params_dictionary["rpc_ip"], + node_params_dictionary["rpc_port"]), timeout=120) + proxy_connected.append(proxy) + except Exception as e: + raise Exception("Connection error! Probably no daemon on selected port. Error: ", e) + return proxy + + yield _proxy_connection + + for each in proxy_connected: + print("\nStopping created proxies...") + time.sleep(10) # time wait for tests to finish correctly before stopping daemon + try: # while using AuthServiceProxy, stop method results in connection aborted error + each.stop() + except ConnectionAbortedError as e: + print(e) + + +@pytest.fixture(scope='session') +def test_params(proxy_connection): + with open('nodesconfig.json', 'r') as f: + params_dict = json.load(f) + node1_params = params_dict['node1'] + node2_params = params_dict['node2'] + rpc1 = proxy_connection(node1_params) + rpc2 = proxy_connection(node2_params) + test_params = {'node1': node1_params, 'node2': node2_params} + test_params['node1'].update({'rpc': rpc1}) + test_params['node2'].update({'rpc': rpc2}) + return test_params diff --git a/qa/pytest_komodo/nodesconfig.json b/qa/pytest_komodo/nodesconfig.json new file mode 100644 index 00000000000..cd534adda14 --- /dev/null +++ b/qa/pytest_komodo/nodesconfig.json @@ -0,0 +1,23 @@ +{ + "node1" : { + "rpc_user" : "test", + "rpc_password" : "test", + "rpc_ip" : "127.0.0.1", + "rpc_port": 7000, + "net_port": 6000, + "pubkey" : "02f0ec2d3da51b09e4fc8d9ba334c275b02b3ab6f22ce7be0ea5059cbccbd1b8c7", + "address": "RPWhA4f4ZTZxNi5K36bcwsWdVjSVDSjUnd", + "txid": "e9945094eb1a9028afd310719aea21966425ceccc3f8c0972277a96201ca1b81" + }, + "node2" : { + "rpc_user" : "test", + "rpc_password" : "test", + "rpc_ip" : "127.0.0.1", + "rpc_port": 7001, + "net_port": 6001, + "pubkey" : "0285f68aec0e2f8b5e817d71a2a20a1fda74ea9943c752a13136a3a30fa49c0149", + "address": "RHoTHYiHD8TU4k9rbY4Aoj3ztxUARMJikH", + "txid": "3376a48a7daf543d8401e072102d4e93f53f300a101bd67e82820d2da0f2729e" + }, + "is_fresh_chain": true +} \ No newline at end of file diff --git a/qa/pytest_komodo/start_ci.bat b/qa/pytest_komodo/start_ci.bat new file mode 100644 index 00000000000..ad430fd23d5 --- /dev/null +++ b/qa/pytest_komodo/start_ci.bat @@ -0,0 +1,15 @@ +set CLIENTS=2 +set CHAIN=TONYCI +set TEST_ADDY0=RPWhA4f4ZTZxNi5K36bcwsWdVjSVDSjUnd +set TEST_WIF0=UpcQympViQpLmv1WzMwszKPrmKUa28zsv8pdLCMgNMXDFBBBKxCN +set TEST_PUBKEY0=02f0ec2d3da51b09e4fc8d9ba334c275b02b3ab6f22ce7be0ea5059cbccbd1b8c7 +set TEST_ADDY1=RHoTHYiHD8TU4k9rbY4Aoj3ztxUARMJikH +set TEST_WIF1=UwmmwgfXwZ673brawUarPzbtiqjsCPWnG311ZRAL4iUCZLBLYeDu +set TEST_PUBKEY1=0285f68aec0e2f8b5e817d71a2a20a1fda74ea9943c752a13136a3a30fa49c0149 +set CHAIN_MODE=REGULAR +set IS_BOOTSTRAP_NEEDED=True +set BOOTSTRAP_URL=https://sirseven.me/share/bootstrap.tar.gz + +python.exe chainstart.py + +python.exe -m pytest %* -s -vv diff --git a/qa/rpc-tests/cc_pytest/README.md b/qa/rpc-tests/cc_pytest/README.md deleted file mode 100644 index 30b2a50f232..00000000000 --- a/qa/rpc-tests/cc_pytest/README.md +++ /dev/null @@ -1,32 +0,0 @@ -Updated RPC unit-tests infrastructure for Antara smart-chain custom modules - -Using pytest as testing framework and slickrpc as rpc proxy. No more python2 support. - -To start just set test nodes RPC credentials in `test_config.json`. -I thought such config usage might be useful as in some manual testing plays as well as in some CI configuration tests integration. - -`is_fresh_chain=False` param allows to run tests on existing chains (it skips some tests which expecting first CC usage on chain) - -So yes - you can run these tests on existing chains, just RPC creds (and wallets with some balance) needed. - -# Dependencies - -`pip3 install setuptools wheel slick-bitcoinrpc pytest wget` - -# Usage - -In `~/komodo/qa/rpc-tests/cc_pytest` directory: - -`python3 -m pytest -s` - starts all tests suite -`python3 -m pytest test_dice.py -s` - starts specific test, dice in this case - -`-s` flag is optional, just displaying python prints which might be helpful in debugging - -`ci_test.sh` script will start a all CCs full test suite from bootstrapped chain - best way to start the tests - -The `start_chains.py` script can spin needed amount of nodes and start the test chain. -You can find an example of this script usage in `ci_setup.sh`. Don't forget to change `test_config.json` accordingly to the chain params. - -Also there is bootstrap downloading functionality in `start_chains.py` what should be quite useful for automated testing setups - -Happy testing! <3 \ No newline at end of file diff --git a/qa/rpc-tests/cc_pytest/ci_setup.sh b/qa/rpc-tests/cc_pytest/ci_setup.sh deleted file mode 100755 index 9df2efdfaf0..00000000000 --- a/qa/rpc-tests/cc_pytest/ci_setup.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash - -# chains start script params -export CLIENTS=2 -export CHAIN="TONYCI" -export TEST_ADDY="RPWhA4f4ZTZxNi5K36bcwsWdVjSVDSjUnd" -export TEST_WIF="UpcQympViQpLmv1WzMwszKPrmKUa28zsv8pdLCMgNMXDFBBBKxCN" -export TEST_PUBKEY="02f0ec2d3da51b09e4fc8d9ba334c275b02b3ab6f22ce7be0ea5059cbccbd1b8c7" -export TEST_ADDY2="RHoTHYiHD8TU4k9rbY4Aoj3ztxUARMJikH" -export TEST_WIF2="UwmmwgfXwZ673brawUarPzbtiqjsCPWnG311ZRAL4iUCZLBLYeDu" -export TEST_PUBKEY2="0285f68aec0e2f8b5e817d71a2a20a1fda74ea9943c752a13136a3a30fa49c0149" -export CHAIN_MODE="REGULAR" -export IS_BOOTSTRAP_NEEDED="True" -export BOOTSTRAP_URL="http://159.69.45.70/bootstrap.tar.gz" - -# starting the chains -python3 start_chains.py - -# starting the tests -python3 -m pytest -s diff --git a/qa/rpc-tests/cc_pytest/start_chains.py b/qa/rpc-tests/cc_pytest/start_chains.py deleted file mode 100644 index 143d29474bb..00000000000 --- a/qa/rpc-tests/cc_pytest/start_chains.py +++ /dev/null @@ -1,147 +0,0 @@ -import os -import time -import sys -import subprocess -import wget -import tarfile -from slickrpc import Proxy - - -clients_to_start = int(os.environ['CLIENTS']) -ac_name = os.environ['CHAIN'] -# node1 params -test_address = os.environ['TEST_ADDY'] -test_wif = os.environ['TEST_WIF'] -test_pubkey = os.environ['TEST_PUBKEY'] -# node2 params -test_address2 = os.environ['TEST_ADDY2'] -test_wif2 = os.environ['TEST_WIF2'] -test_pubkey2 = os.environ['TEST_PUBKEY2'] -# expecting REGTEST or REGULAR there -chain_start_mode = os.environ['CHAIN_MODE'] -# expecting True or False string there -is_boostrap_needed = os.environ['IS_BOOTSTRAP_NEEDED'] -bootstrap_url = os.environ['BOOTSTRAP_URL'] - -# pre-creating separate folders and configs -for i in range(clients_to_start): - os.mkdir("node_" + str(i)) - open("node_" + str(i) + "/" + ac_name + ".conf", 'a').close() - with open("node_" + str(i) + "/" + ac_name + ".conf", 'a') as conf: - conf.write("rpcuser=test" + '\n') - conf.write("rpcpassword=test" + '\n') - conf.write("rpcport=" + str(7000 + i) + '\n') - conf.write("rpcbind=0.0.0.0\n") - conf.write("rpcallowip=0.0.0.0/0\n") - - -if is_boostrap_needed == "True": - wget.download(bootstrap_url, "bootstrap.tar.gz") - tf = tarfile.open("bootstrap.tar.gz") - for i in range(clients_to_start): - tf.extractall("node_" + str(i)) - -# start numnodes daemons, changing folder name and port -for i in range(clients_to_start): - # all nodes should search for first "mother" node - if i == 0: - start_args = ['../../../src//komodod', '-ac_name='+ac_name, '-ac_reward=100000000000', '-conf=' + sys.path[0] + '/node_' + str(i) + "/" + ac_name + ".conf", - '-rpcport=' + str(7000 + i), '-port=' + str(6000 + i), '-datadir=' + sys.path[0] + '/node_' + str(i), - '-ac_supply=10000000000', '-ac_cc=2', '-pubkey=' + test_pubkey, '-whitelist=127.0.0.1'] - if chain_start_mode == 'REGTEST': - start_args.append('-regtest') - start_args.append('-daemon') - else: - start_args.append('-daemon') - subprocess.call(start_args) - time.sleep(5) - else: - start_args = ['../../../src//komodod', '-ac_name='+ac_name, '-ac_reward=100000000000', '-conf=' + sys.path[0] + '/node_' + str(i) + "/" + ac_name + ".conf", - '-rpcport=' + str(7000 + i), '-port=' + str(6000 + i), '-datadir=' + sys.path[0] + '/node_' + str(i), - '-ac_supply=10000000000', '-ac_cc=2', '-addnode=127.0.0.1:6000', '-whitelist=127.0.0.1', '-listen=0', '-pubkey='+test_pubkey] - if i == 1: - start_args.append('-pubkey=' + test_pubkey2) - if chain_start_mode == 'REGTEST': - start_args.append('-regtest') - start_args.append('-daemon') - else: - start_args.append('-daemon') - subprocess.call(start_args) - time.sleep(5) - - -# creating rpc proxies for all nodes -for i in range(clients_to_start): - rpcport = 7000 + i - globals()['proxy_%s' % i] = Proxy("http://%s:%s@127.0.0.1:%d"%("test", "test", int(rpcport))) -time.sleep(2) - - -# checking if proxies works as expected -for i in range(clients_to_start): - while True: - try: - getinfo_output = globals()['proxy_%s' % i].getinfo() - print(getinfo_output) - break - except Exception as e: - print(e) - time.sleep(2) - -# in case of bootstrap checking also if blocksamount > 100 -if is_boostrap_needed == "True": - assert proxy_0.getinfo()["blocks"] > 100 - assert proxy_1.getinfo()["blocks"] > 100 - assert proxy_0.getinfo()["blocks"] == proxy_1.getinfo()["blocks"] - - -# importing privkeys -print("IMPORTING PRIVKEYS") -print(proxy_0.importprivkey(test_wif, "", True)) -print(proxy_1.importprivkey(test_wif2, "", True)) -# ensuring that node1 got premine and some balance -assert proxy_0.getbalance() > 777 - -# checking if test addys belongs to relevant nodes wallets -assert proxy_0.validateaddress(test_address)["ismine"] == True -assert proxy_1.validateaddress(test_address2)["ismine"] == True - -# checking if pubkeys set properly -assert proxy_0.getinfo()["pubkey"] == test_pubkey -assert proxy_1.getinfo()["pubkey"] == test_pubkey2 - -# starting blocks creation on second node, mining rewards will get first public node because of pubkey param -if chain_start_mode == 'REGTEST': - while True: - if int(os.environ['CLIENTS']) > 1: - proxy_1.generate(1) - time.sleep(5) - else: - proxy_0.generate(1) - time.sleep(5) -else: - if int(os.environ['CLIENTS']) > 1: - print("Starting mining on node 2") - proxy_1.setgenerate(True, 1) - # just to ensure better mempool propagation - print("Starting mining on node 1") - proxy_0.setgenerate(True, 1) - else: - print("Starting mining on node 1") - proxy_0.setgenerate(True, 1) - -# TODO: just to prepare a boostrap if needed -# while True: -# blocks_amount = proxy_0.getinfo()["blocks"] -# if blocks_amount == 130: -# balance = proxy_0.getbalance() -# tx_id = proxy_0.sendtoaddress(test_address, balance - 0.1) -# elif blocks_amount > 130: -# print("Finished with blocks pre-generation") -# proxy_0.stop() -# proxy_1.stop() -# time.sleep(2) -# sys.exit() -# else: -# print(proxy_0.getinfo()["blocks"]) -# time.sleep(5) diff --git a/qa/rpc-tests/cc_pytest/test_config.json b/qa/rpc-tests/cc_pytest/test_config.json deleted file mode 100644 index a282544265f..00000000000 --- a/qa/rpc-tests/cc_pytest/test_config.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "node1" : { - "rpc_user" : "test", - "rpc_password" : "test", - "rpc_ip" : "127.0.0.1", - "rpc_port": 7000, - "pubkey" : "02f0ec2d3da51b09e4fc8d9ba334c275b02b3ab6f22ce7be0ea5059cbccbd1b8c7" - }, - "node2" : { - "rpc_user" : "test", - "rpc_password" : "test", - "rpc_ip" : "127.0.0.1" , - "rpc_port": 7001, - "pubkey" : "0285f68aec0e2f8b5e817d71a2a20a1fda74ea9943c752a13136a3a30fa49c0149" - }, - "is_fresh_chain" : true -} \ No newline at end of file