├── VERSION ├── tests ├── simpleScore │ ├── __init__.py │ ├── package.json │ └── simpleScore.py ├── simpleScore2 │ ├── __init__.py │ ├── tests │ │ ├── __init__.py │ │ └── test_z_integrate_simpleScore2.py │ ├── package.json │ └── simpleScore2.py ├── test_util │ ├── test_in_memory_zip │ │ ├── test_a │ │ └── test_b │ ├── call.json │ ├── send_wo_steplimit.json │ ├── send.json │ ├── test_tbears_cli_config.json │ ├── keystore │ ├── test_keystore │ ├── test_icon_config │ │ ├── test_tbears_cli_config.json │ │ ├── test_cli_config_keystore │ │ └── test_tbears_server_config.json │ ├── test_tbears_cli_config_step_set.json │ ├── test_tbears_server_config.json │ └── __init__.py ├── __init__.py ├── test_parsing_command.py ├── test_keystore.py ├── test_keystore_manager.py ├── test_parsing_server.py ├── test_prep_manager.py ├── test_tbears_db.py ├── test_score_test_command.py ├── test_tbears_service.py ├── test_parsing_util.py ├── test_argparse_type.py ├── test_parsing_score.py ├── test_block.py └── test_command_wallet.py ├── setup.cfg ├── Docker ├── genesis │ ├── init.sh │ └── install.sh ├── entry.sh ├── Makefile ├── Dockerfile ├── tbears_server_config.json └── README.md ├── images └── components.png ├── requirements.txt ├── tbears ├── data │ └── mainnet.tar.gz ├── tools │ ├── mainnet │ │ ├── data │ │ │ ├── governance-0.0.5.zip │ │ │ ├── governance_0.0.5.zip │ │ │ ├── governance_0.0.6.zip │ │ │ ├── governance_0.0.7.zip │ │ │ ├── governance_1.0.0.zip │ │ │ └── governance_1.1.1.zip │ │ ├── README │ │ ├── __init__.py │ │ └── tbears_server_config.json │ └── __init__.py ├── util │ ├── arg_parser.py │ ├── keystore_manager.py │ ├── transaction_logger.py │ ├── argparse_type.py │ └── __init__.py ├── block_manager │ ├── __init__.py │ ├── __main__.py │ ├── hash_utils.py │ ├── tx_verifier.py │ ├── icon_service.py │ ├── tbears_db.py │ ├── task.py │ ├── message_code.py │ └── channel_service.py ├── libs │ ├── __init__.py │ └── scoretest │ │ ├── __init__.py │ │ ├── mock │ │ ├── __init__.py │ │ ├── block.py │ │ ├── icx_engine.py │ │ └── db.py │ │ ├── patch │ │ ├── __init__.py │ │ └── context.py │ │ └── score_test_case.py ├── command │ ├── __init__.py │ ├── command.py │ └── command_util.py ├── profile_tbears │ ├── __init__.py │ └── startup │ │ ├── __init__.py │ │ └── 00_first.py ├── config │ └── __init__.py ├── __init__.py ├── __main__.py ├── tbears_cli.py └── tbears_exception.py ├── docs ├── build.sh ├── index.rst ├── Makefile ├── tbears_blockmanager.wsd ├── conf.py └── score_integration_test.md ├── test.sh ├── .travis.yml ├── deploy.sh ├── setup.py ├── .gitignore └── CHANGELOG.md /VERSION: -------------------------------------------------------------------------------- 1 | 1.8.0 2 | -------------------------------------------------------------------------------- /tests/simpleScore/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/simpleScore2/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/simpleScore2/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal = 0 3 | -------------------------------------------------------------------------------- /tests/test_util/test_in_memory_zip/test_a: -------------------------------------------------------------------------------- 1 | aaa -------------------------------------------------------------------------------- /tests/test_util/test_in_memory_zip/test_b: -------------------------------------------------------------------------------- 1 | bbb -------------------------------------------------------------------------------- /Docker/genesis/init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | tbears sync_mainnet 4 | -------------------------------------------------------------------------------- /images/components.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icon-project/t-bears/HEAD/images/components.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | iconrpcserver~=1.6.0 2 | iconservice~=1.8.9 3 | iconsdk~=1.3.4 4 | ipython>=6.4.0 5 | -------------------------------------------------------------------------------- /tbears/data/mainnet.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icon-project/t-bears/HEAD/tbears/data/mainnet.tar.gz -------------------------------------------------------------------------------- /Docker/entry.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | service rabbitmq-server start 4 | 5 | tbears genconf 6 | tbears start 7 | 8 | exec /bin/bash 9 | -------------------------------------------------------------------------------- /tests/simpleScore/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.0.1", 3 | "main_module": "simpleScore", 4 | "main_score": "SimpleScore" 5 | } -------------------------------------------------------------------------------- /tests/simpleScore2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.0.1", 3 | "main_module": "simpleScore2", 4 | "main_score": "SimpleScore2" 5 | } -------------------------------------------------------------------------------- /tbears/tools/mainnet/data/governance-0.0.5.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icon-project/t-bears/HEAD/tbears/tools/mainnet/data/governance-0.0.5.zip -------------------------------------------------------------------------------- /tbears/tools/mainnet/data/governance_0.0.5.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icon-project/t-bears/HEAD/tbears/tools/mainnet/data/governance_0.0.5.zip -------------------------------------------------------------------------------- /tbears/tools/mainnet/data/governance_0.0.6.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icon-project/t-bears/HEAD/tbears/tools/mainnet/data/governance_0.0.6.zip -------------------------------------------------------------------------------- /tbears/tools/mainnet/data/governance_0.0.7.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icon-project/t-bears/HEAD/tbears/tools/mainnet/data/governance_0.0.7.zip -------------------------------------------------------------------------------- /tbears/tools/mainnet/data/governance_1.0.0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icon-project/t-bears/HEAD/tbears/tools/mainnet/data/governance_1.0.0.zip -------------------------------------------------------------------------------- /tbears/tools/mainnet/data/governance_1.1.1.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icon-project/t-bears/HEAD/tbears/tools/mainnet/data/governance_1.1.1.zip -------------------------------------------------------------------------------- /Docker/genesis/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | USE_PYPI=true 4 | 5 | if [ $USE_PYPI == true ]; then 6 | pip3 install tbears 7 | else 8 | WORKDIR=$(dirname $0) 9 | FILES=$(echo *.whl) 10 | for f in $FILES; do 11 | pip3 install $WORKDIR/$f 12 | done 13 | fi 14 | -------------------------------------------------------------------------------- /tbears/tools/mainnet/README: -------------------------------------------------------------------------------- 1 | 1. run 2 | $ python sync.py 3 | 4 | 2. output 5 | - generate tarball file for tbears `sync_mainnet` command 6 | $ ls tbears/data/mainnet.tar.gz 7 | 8 | 3. update revision and governance SCORE configuration 9 | - append an entry to sync_list@sync.py 10 | -------------------------------------------------------------------------------- /tbears/util/arg_parser.py: -------------------------------------------------------------------------------- 1 | import re 2 | from urllib import parse 3 | 4 | 5 | def uri_parser(uri: str) -> (str, int): 6 | 7 | uri = parse.urlparse(uri) 8 | _uri = uri.scheme + '://' + uri.netloc 9 | _version = re.search(r'(?<=\bv)\d+', uri.path).group(0) 10 | 11 | return _uri, int(_version) 12 | -------------------------------------------------------------------------------- /tests/test_util/call.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "method": "icx_call", 4 | "params": { 5 | "from": "hxef73db5d0ad02eb1fadb37d0041be96bfa56d4e6", 6 | "to": "cx0000000000000000000000000000000000000001", 7 | "dataType": "call", 8 | "data": { 9 | "method": "getStepPrice" 10 | } 11 | }, 12 | "id": 1 13 | } -------------------------------------------------------------------------------- /tests/test_util/send_wo_steplimit.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "method": "icx_sendTransaction", 4 | "params": { 5 | "version": "0x3", 6 | "from": "hxef73db5d0ad02eb1fadb37d0041be96bfa56d4e6", 7 | "value": "0xde0b6b3a7640000", 8 | "nid": "0x3", 9 | "nonce": "0x1", 10 | "to": "hxaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 11 | }, 12 | "id": 1 13 | } 14 | -------------------------------------------------------------------------------- /tests/test_util/send.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "method": "icx_sendTransaction", 4 | "params": { 5 | "version": "0x3", 6 | "from": "hxef73db5d0ad02eb1fadb37d0041be96bfa56d4e6", 7 | "value": "0xde0b6b3a7640000", 8 | "stepLimit": "0x3000000", 9 | "nid": "0x3", 10 | "nonce": "0x1", 11 | "to": "hxaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 12 | }, 13 | "id": 1 14 | } 15 | -------------------------------------------------------------------------------- /tests/test_util/test_tbears_cli_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "uri": "http://127.0.0.1:9000/api/v3", 3 | "nid": "0x3", 4 | "keyStore": null, 5 | "from": "hxaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 6 | "to": "cx0000000000000000000000000000000000000000", 7 | "stepLimit": "0x300000", 8 | "deploy": { 9 | "mode": "install", 10 | "scoreParams": {} 11 | }, 12 | "txresult": {}, 13 | "transfer": {} 14 | } -------------------------------------------------------------------------------- /docs/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | PYVER=$(python -c 'import sys; print(sys.version_info[0])') 4 | if [[ PYVER -ne 3 ]];then 5 | echo "The script should be run on python3" 6 | exit 1 7 | fi 8 | 9 | pip3 show sphinx 1>/dev/null 10 | 11 | if [ $? != 0 ];then 12 | pip3 install sphinx==1.7.5 13 | fi 14 | pip3 show sphinx_rtd_theme 1>/dev/null 15 | 16 | if [ $? != 0 ];then 17 | pip3 install sphinx_rtd_theme==0.4.0 18 | fi 19 | 20 | sphinx-apidoc -o ./ ../ ../setup.py ../tests/ 21 | make html 22 | -------------------------------------------------------------------------------- /tests/test_util/keystore: -------------------------------------------------------------------------------- 1 | {"version":3,"id":"a7882153-5986-4aa9-bdee-f5b7bae620b5","address":"hx4873b94352c8c1f3b2f09aaeccea31ce9e90bd31","crypto":{"ciphertext":"0a1728b0a1336593c5f2b731bbc3ebb585af4444fff016d4810172ce9034a4d5","cipherparams":{"iv":"fd0d9729dede18706a4967c4bcf0f3eb"},"cipher":"aes-128-ctr","kdf":"scrypt","kdfparams":{"dklen":32,"salt":"48121cb0561587eb67f5cf9326970c581af9d572b06270e7fff926ae92374506","n":16384,"r":8,"p":1},"mac":"9206a6cc4b3c2a20599c8d1d35331b4ce321ac5eab758b67e1e0f4b66514b880"},"coinType":"icx"} -------------------------------------------------------------------------------- /tests/test_util/test_keystore: -------------------------------------------------------------------------------- 1 | {"address": "hxef73db5d0ad02eb1fadb37d0041be96bfa56d4e6", "crypto": {"cipher": "aes-128-ctr", "cipherparams": {"iv": "54e4ab5b171f46b256169265e778a6a5"}, "ciphertext": "6e2adee029b9a186d3532470bad6e5f58d4505f2847a374d9b478fe57c090be1", "kdf": "scrypt", "kdfparams": {"dklen": 32, "n": 262144, "r": 1, "p": 8, "salt": "6784799b6e2868b757c9eb86fdbeb40a"}, "mac": "9b3c5f63d3bf8f1505836f677bb89ac5d4c3e2a177efe071531ed4e4b2c06ab6"}, "id": "e194034e-bf00-460a-96bf-f3b4338a4e30", "version": 3, "coinType": "icx"} -------------------------------------------------------------------------------- /tests/test_util/test_icon_config/test_tbears_cli_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "uri": "http://127.0.0.1:9000/api/v3_config_path", 3 | "nid": "0x3", 4 | "keyStore": null, 5 | "from": "hxaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 6 | "to": "cx0000000000000000000000000000000000000000", 7 | "deploy": { 8 | "stepLimit": "0x3000000", 9 | "mode": "install_config_path", 10 | "scoreParams": {} 11 | }, 12 | "txresult": {}, 13 | "transfer": { 14 | "stepLimit": "0xf4240" 15 | } 16 | } -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. tbears documentation master file, created by 2 | sphinx-quickstart on Wed Jul 11 08:52:22 2018. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to tbears's documentation! 7 | ================================== 8 | 9 | .. toctree:: 10 | :maxdepth: 2 11 | :caption: Contents: 12 | 13 | 14 | 15 | Indices and tables 16 | ================== 17 | 18 | * :ref:`genindex` 19 | * :ref:`modindex` 20 | * :ref:`search` 21 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | if [ $# -ne 1 ] 5 | then 6 | echo "Usage: test.sh " 7 | exit 1 8 | fi 9 | 10 | HOST=tbears.icon.foundation 11 | S3_HOST="$HOST.s3-website.ap-northeast-2.amazonaws.com" 12 | DEPS="earlgrey iconcommons iconservice iconrpcserver" 13 | BRANCH=$1 14 | 15 | for PKG in $DEPS 16 | do 17 | URL="http://$S3_HOST/$BRANCH/$PKG" 18 | VERSION=$(curl "$URL/VERSION") 19 | FILE="$PKG-$VERSION-py3-none-any.whl" 20 | pip install --force-reinstall "$URL/$FILE" 21 | done 22 | 23 | python setup.py test 24 | -------------------------------------------------------------------------------- /tests/test_util/test_icon_config/test_cli_config_keystore: -------------------------------------------------------------------------------- 1 | {"version":3,"id":"a7882153-5986-4aa9-bdee-f5b7bae620b5","address":"hx4873b94352c8c1f3b2f09aaeccea31ce9e90bd31","crypto":{"ciphertext":"0a1728b0a1336593c5f2b731bbc3ebb585af4444fff016d4810172ce9034a4d5","cipherparams":{"iv":"fd0d9729dede18706a4967c4bcf0f3eb"},"cipher":"aes-128-ctr","kdf":"scrypt","kdfparams":{"dklen":32,"salt":"48121cb0561587eb67f5cf9326970c581af9d572b06270e7fff926ae92374506","n":16384,"r":8,"p":1},"mac":"9206a6cc4b3c2a20599c8d1d35331b4ce321ac5eab758b67e1e0f4b66514b880"},"coinType":"icx"} -------------------------------------------------------------------------------- /Docker/Makefile: -------------------------------------------------------------------------------- 1 | VERSION=$(shell cat ../VERSION) 2 | IMAGE_TAG=iconloop/tbears:$(VERSION) 3 | LISTEN_PORT=9000 4 | RC_REPO=icon-project/rewardcalculator 5 | RC_TAG=$(shell curl --silent "https://api.github.com/repos/$(RC_REPO)/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') 6 | 7 | build: 8 | docker build -t $(IMAGE_TAG) --build-arg RC_TAG=$(RC_TAG) . 9 | 10 | run: 11 | docker run -it -p $(LISTEN_PORT):9000 --rm $(IMAGE_TAG) 12 | 13 | shell: 14 | docker run -it -p $(LISTEN_PORT):9000 --rm --entrypoint /bin/bash $(IMAGE_TAG) 15 | -------------------------------------------------------------------------------- /tests/test_util/test_tbears_cli_config_step_set.json: -------------------------------------------------------------------------------- 1 | { 2 | "uri": "http://127.0.0.1:9000/api/v3", 3 | "nid": "0x3", 4 | "keyStore": null, 5 | "from": "hxef73db5d0ad02eb1fadb37d0041be96bfa56d4e6", 6 | "to": "cx0000000000000000000000000000000000000000", 7 | "deploy": { 8 | "mode": "install", 9 | "scoreParams": {}, 10 | "stepLimit": "0x1" 11 | }, 12 | "txresult": {}, 13 | "transfer": { 14 | "stepLimit": "0x1" 15 | }, 16 | "sendtx": { 17 | "stepLimit": "0x1" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tbears/block_manager/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 ICON Foundation 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | dist: bionic 3 | python: 4 | - "3.7" 5 | 6 | addons: 7 | apt: 8 | packages: 9 | - rabbitmq-server 10 | services: 11 | - rabbitmq 12 | 13 | branches: 14 | only: 15 | - master 16 | - develop 17 | - /^release-.*$/ 18 | - /^[0-9]+\.[0-9]+\.[0-9]+$/ 19 | - travis-test 20 | 21 | install: 22 | - go get github.com/icon-project/rewardcalculator/cmd/icon_rc 23 | - pip install -r requirements.txt 24 | 25 | script: 26 | - python setup.py test 27 | 28 | deploy: 29 | provider: pypi 30 | distributions: sdist bdist_wheel 31 | username: $PYPI_USER 32 | password: $PYPI_PW 33 | on: 34 | tags: true 35 | -------------------------------------------------------------------------------- /tbears/libs/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright 2018 ICON Foundation 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright 2017-2018 ICON Foundation 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | -------------------------------------------------------------------------------- /tbears/command/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright 2018 ICON Foundation 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | -------------------------------------------------------------------------------- /tbears/profile_tbears/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright 2018 ICON Foundation 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. -------------------------------------------------------------------------------- /tbears/tools/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright 2018 ICON Foundation 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | -------------------------------------------------------------------------------- /tbears/config/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright 2018 ICON Foundation 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | -------------------------------------------------------------------------------- /tbears/tools/mainnet/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright 2018 ICON Foundation 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | -------------------------------------------------------------------------------- /tbears/profile_tbears/startup/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright 2018 ICON Foundation 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SPHINXPROJ = tbears 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /tbears/libs/scoretest/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright 2018 ICON Foundation 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | -------------------------------------------------------------------------------- /tbears/libs/scoretest/mock/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright 2018 ICON Foundation 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | -------------------------------------------------------------------------------- /tbears/libs/scoretest/patch/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright 2018 ICON Foundation 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | -------------------------------------------------------------------------------- /tbears/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright 2018 ICON Foundation 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | from .tbears_cli import main 16 | 17 | if __name__ == "__main__": 18 | main() 19 | -------------------------------------------------------------------------------- /tests/simpleScore/simpleScore.py: -------------------------------------------------------------------------------- 1 | from iconservice import * 2 | 3 | 4 | class SimpleScore(IconScoreBase): 5 | 6 | def __init__(self, db: IconScoreDatabase) -> None: 7 | super().__init__(db) 8 | self.value = VarDB("value", db, value_type=str) 9 | 10 | @eventlog() 11 | def SetValue(self, value: str): pass 12 | 13 | def on_install(self) -> None: 14 | super().on_install() 15 | 16 | def on_update(self) -> None: 17 | super().on_update() 18 | self.value.set("updated value") 19 | 20 | @external(readonly=True) 21 | def hello(self) -> str: 22 | return "Hello" 23 | 24 | @external 25 | def setValue(self, value: str): 26 | self.value.set(value) 27 | 28 | self.SetValue(value) 29 | 30 | @external(readonly=True) 31 | def getValue(self) -> str: 32 | return self.value.get() 33 | -------------------------------------------------------------------------------- /tbears/__main__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright 2018 ICON Foundation 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | import sys 18 | from .tbears_cli import main 19 | 20 | 21 | if __name__ == "__main__": 22 | try: 23 | sys.exit(main()) 24 | except KeyboardInterrupt: 25 | print("exit") 26 | -------------------------------------------------------------------------------- /tbears/block_manager/__main__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright 2018 ICON Foundation 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | import sys 18 | from tbears.block_manager.block_manager import main 19 | 20 | 21 | if __name__ == "__main__": 22 | try: 23 | sys.exit(main()) 24 | except KeyboardInterrupt: 25 | print("exit") 26 | -------------------------------------------------------------------------------- /tbears/tbears_cli.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright 2018 ICON Foundation 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | import sys 16 | 17 | from tbears.command.command import Command 18 | from tbears.tbears_exception import TBearsExceptionCode 19 | 20 | 21 | def main(): 22 | cmd = Command() 23 | 24 | # tbears starting point 25 | result = cmd.run(sys.argv[1:]) 26 | 27 | if isinstance(result, int) is False: 28 | sys.exit(TBearsExceptionCode.OK.value) 29 | 30 | sys.exit(result) 31 | -------------------------------------------------------------------------------- /tbears/util/keystore_manager.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright 2018 ICON Foundation 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | import re 16 | 17 | 18 | def validate_password(password) -> bool: 19 | """Verify the entered password. 20 | 21 | :param password: The password the user entered. type(str) 22 | :return: bool 23 | True: When the password is valid format 24 | False: When the password is invalid format 25 | """ 26 | 27 | return bool(re.match(r'^(?=.*\d)(?=.*[a-zA-Z])(?=.*[?!:\.,%+-/*<>{}\(\)\[\]`"\'~_^\\|@#$&]).{8,}$', password)) 28 | -------------------------------------------------------------------------------- /Docker/Dockerfile: -------------------------------------------------------------------------------- 1 | # Build rewardcalculator 2 | FROM golang:1.12-alpine as builder 3 | 4 | ARG RC_TAG 5 | 6 | RUN apk add --no-cache git make 7 | ADD https://api.github.com/repos/icon-project/rewardcalculator/git/refs/heads/master version.json 8 | RUN git clone --branch $RC_TAG --single-branch https://github.com/icon-project/rewardcalculator.git /rewardcalculator 9 | 10 | RUN cd /rewardcalculator && make 11 | 12 | # Build T-Bears 13 | FROM python:3.7-slim-stretch 14 | 15 | RUN apt-get update && apt-get install -y --no-install-recommends \ 16 | gcc \ 17 | git \ 18 | libc-dev \ 19 | pkg-config \ 20 | rabbitmq-server \ 21 | vim-tiny \ 22 | lsof \ 23 | && rm -rf /var/lib/apt/lists/* 24 | 25 | WORKDIR /work 26 | COPY --from=builder /rewardcalculator/bin/icon_rc /usr/local/bin/ 27 | COPY --from=builder /rewardcalculator/bin/rctool /usr/local/bin/ 28 | COPY entry.sh /usr/local/bin 29 | COPY tbears_server_config.json . 30 | ADD genesis genesis 31 | 32 | RUN ./genesis/install.sh \ 33 | && rm -rf /root/.cache/pip 34 | RUN ./genesis/init.sh \ 35 | && rm -rf genesis 36 | 37 | EXPOSE 9000 38 | ENTRYPOINT ["entry.sh"] 39 | -------------------------------------------------------------------------------- /tbears/libs/scoretest/mock/block.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright 2018 ICON Foundation 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | from time import time 17 | 18 | 19 | TIMESTAMP_FACT = 2 * 10 ** 6 20 | 21 | 22 | class Block: 23 | def __init__(self, height: int=0): 24 | self.start_time = int(time()*10**6) 25 | self.height = height 26 | self.timestamp = self.start_time + height * TIMESTAMP_FACT 27 | 28 | @property 29 | def _height(self): 30 | return self.height 31 | 32 | @_height.setter 33 | def _height(self, height): 34 | self.height = height 35 | self.timestamp = self.start_time + self.height * TIMESTAMP_FACT 36 | -------------------------------------------------------------------------------- /tbears/block_manager/hash_utils.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 ICON Foundation 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | from iconservice.utils.hashing.hash_generator import HashGenerator, RootHashGenerator 15 | 16 | TRANSACTION_HASH_SALT = "icx_sendTransaction" 17 | 18 | 19 | def generate_hash(data: list, salt: str = TRANSACTION_HASH_SALT): 20 | if not data: 21 | return "0" * 64 22 | HashGenerator._SALT = salt 23 | values = [] 24 | for elem in data: 25 | if isinstance(elem, dict): 26 | value = HashGenerator.generate_hash(elem) 27 | elif elem is None: 28 | continue 29 | else: 30 | value = elem 31 | values.append(value) 32 | return RootHashGenerator.generate_root_hash(values, True).hex() 33 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | HOST="tbears.icon.foundation" 5 | PRODUCT="tbears" 6 | 7 | # Check arguments 8 | if [ $# -eq 1 ]; then 9 | BRANCH=$1 10 | elif [ $# -eq 2 ]; then 11 | BRANCH=$1 12 | VERSION=$2 13 | else 14 | echo "Usage: deploy.sh [version]" 15 | echo "- : (required) git branch" 16 | echo "- [version]: (optional) Package version" 17 | echo "- ex) ./deploy.sh master 0.9.6" 18 | exit 1 19 | fi 20 | 21 | # Check python version >= 3.0 22 | PYVER=$(python -c 'import sys; print(sys.version_info[0])') 23 | if [[ PYVER -ne 3 ]];then 24 | echo "The script should be run on python3" 25 | exit 2 26 | fi 27 | 28 | # Get default version from VERSION file 29 | if [ -z $VERSION ]; then 30 | VERSION=$(cat VERSION) 31 | fi 32 | 33 | echo "$PRODUCT $BRANCH $VERSION" 34 | 35 | if [[ -z "${AWS_ACCESS_KEY_ID}" || -z "${AWS_SECRET_ACCESS_KEY}" ]]; then 36 | echo "Error: AWS keys should be in your environment" 37 | exit 1 38 | fi 39 | 40 | S3_URL="s3://${HOST}/${BRANCH}/${PRODUCT}" 41 | echo "$S3_URL" 42 | 43 | pip install awscli 44 | aws s3 cp dist/*$VERSION*.whl "$S3_URL/" --acl public-read 45 | 46 | if [ -z $VERSION ] 47 | then 48 | aws s3 cp VERSION "$S3_URL/" --acl public-read 49 | else 50 | echo "$VERSION" > .VERSION 51 | aws s3 cp .VERSION "$S3_URL/VERSION" --acl public-read 52 | rm -f .VERSION 53 | fi 54 | -------------------------------------------------------------------------------- /tbears/util/transaction_logger.py: -------------------------------------------------------------------------------- 1 | from iconcommons.logger.logger import Logger 2 | 3 | from tbears.config.tbears_config import TBEARS_CLI_TAG 4 | 5 | 6 | def send_transaction_with_logger(icon_service, signed_transaction, uri): 7 | tx_dict = make_dict_to_rpc_dict(signed_transaction.signed_transaction_dict, 'icx_transaction') 8 | Logger.info(f"Send request to {uri}. Request body: {tx_dict}", TBEARS_CLI_TAG) 9 | 10 | return icon_service.send_transaction(signed_transaction, True) 11 | 12 | 13 | def call_with_logger(icon_service, call_obj, uri): 14 | call_dict = make_call_dict_to_rpc_dict(call_obj) 15 | Logger.info(f"Send request to {uri}. Request body: {call_dict}", TBEARS_CLI_TAG) 16 | 17 | return icon_service.call(call_obj, True) 18 | 19 | 20 | def make_dict_to_rpc_dict(obj: dict, method): 21 | rpc_dict = { 22 | 'jsonrpc': '2.0', 23 | 'method': method, 24 | 'id': 1234 25 | } 26 | 27 | if obj: 28 | rpc_dict['params'] = obj 29 | 30 | return rpc_dict 31 | 32 | 33 | def make_call_dict_to_rpc_dict(call): 34 | params = { 35 | "to": call.to, 36 | "dataType": "call", 37 | "data": { 38 | "method": call.method 39 | } 40 | } 41 | 42 | if call.from_ is not None: 43 | params["from"] = call.from_ 44 | 45 | if isinstance(call.params, dict): 46 | params["data"]["params"] = call.params 47 | 48 | return make_dict_to_rpc_dict(params, 'icx_call') 49 | -------------------------------------------------------------------------------- /Docker/tbears_server_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "hostAddress": "127.0.0.1", 3 | "port": 9000, 4 | "scoreRootPath": "./.score", 5 | "stateDbRootPath": "./.statedb", 6 | "builtinScoreOwner": "hxe7af5fcfd8dfc67530a01a0e403882687528dfcb", 7 | "log": { 8 | "logger": "tbears", 9 | "level": "info", 10 | "filePath": "./tbears.log", 11 | "colorLog": true, 12 | "outputType": "file", 13 | "rotate": { 14 | "type": "bytes", 15 | "maxBytes": 10485760, 16 | "backupCount": 10 17 | } 18 | }, 19 | "service": { 20 | "fee": true, 21 | "audit": false, 22 | "deployerWhiteList": false 23 | }, 24 | "genesis": { 25 | "nid": "0x3", 26 | "accounts": [ 27 | { 28 | "name": "genesis", 29 | "address": "hx0000000000000000000000000000000000000000", 30 | "balance": "0x2961fff8ca4a62327800000" 31 | }, 32 | { 33 | "name": "fee_treasury", 34 | "address": "hx1000000000000000000000000000000000000000", 35 | "balance": "0x0" 36 | }, 37 | { 38 | "name": "test1", 39 | "address": "hxe7af5fcfd8dfc67530a01a0e403882687528dfcb", 40 | "balance": "0x2961fff8ca4a62327800000" 41 | } 42 | ] 43 | }, 44 | "blockConfirmInterval": 2, 45 | "blockConfirmEmpty": true, 46 | "mainPRepCount": 4, 47 | "iissCalculatePeriod": 30, 48 | "termPeriod": 30 49 | } 50 | -------------------------------------------------------------------------------- /tests/test_util/test_tbears_server_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "hostAddress": "0.0.0.0", 3 | "port": 9000, 4 | "scoreRootPath": "./.score", 5 | "stateDbRootPath": "./.statedb", 6 | "log": { 7 | "colorLog": true, 8 | "level": "debug", 9 | "filePath": "./tbears.log", 10 | "outputType": "file", 11 | "rotate": { 12 | "type": "bytes", 13 | "maxBytes": 10485760, 14 | "backupCount": 10 15 | } 16 | }, 17 | "service": { 18 | "fee": true, 19 | "audit": false, 20 | "deployerWhiteList": false 21 | }, 22 | "genesis": { 23 | "nid": "0x3", 24 | "accounts": [ 25 | { 26 | "name": "genesis", 27 | "address": "hxef73db5d0ad02eb1fadb37d0041be96bfa56d4e6", 28 | "balance": "0x2961fff8ca4a62327800000" 29 | }, 30 | { 31 | "name": "fee_treasury", 32 | "address": "hx1000000000000000000000000000000000000000", 33 | "balance": "0x0" 34 | }, 35 | { 36 | "name": "test1", 37 | "address": "hxe7af5fcfd8dfc67530a01a0e403882687528dfcb", 38 | "balance": "0x2961fff8ca4a62327800000" 39 | } 40 | ] 41 | }, 42 | "channel": "loopchain_default", 43 | "amqpKey": "7100", 44 | "amqpTarget": "127.0.0.1", 45 | "blockConfirmInterval": 1, 46 | "blockConfirmEmpty": false, 47 | "blockGeneratorRotation": true, 48 | "blockGenerateCountPerLeader": 10 49 | } 50 | -------------------------------------------------------------------------------- /tests/test_parsing_command.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 ICON Foundation 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import unittest 16 | import os 17 | import shutil 18 | 19 | from tbears.command.command import Command 20 | 21 | 22 | # command package do 23 | # 1. parsing user's input date 24 | # 2. run something requested 25 | # so we have to check both, first things to do is check parse correctly 26 | class TestCommand(unittest.TestCase): 27 | def setUp(self): 28 | self.cmd = Command() 29 | self.parser = self.cmd.parser 30 | self.subparsers = self.cmd.subparsers 31 | 32 | @staticmethod 33 | def touch(path): 34 | with open(path, 'a'): 35 | os.utime(path, None) 36 | 37 | def tearDown(self): 38 | # tear_down_params' key value (file or directory) is always relative path 39 | for path in self.tear_down_params: 40 | if os.path.isfile(path): 41 | os.remove(path) 42 | elif os.path.isdir(path): 43 | shutil.rmtree(path) 44 | else: 45 | continue 46 | -------------------------------------------------------------------------------- /docs/tbears_blockmanager.wsd: -------------------------------------------------------------------------------- 1 | @startuml T-Bears blockmanager work flow 2 | 3 | actor "Client or T-Bears cli" as client 4 | participant "ICON RPC Server" as rpcserver #lightblue 5 | participant "T-Bears Block Manager" as blockmanager 6 | participant "ICON Service" as icon #lightgreen 7 | 8 | group initialize 9 | blockmanager -> icon : hello 10 | blockmanager -> icon : invoke genesis 11 | icon -> blockmanager : response 12 | note over blockmanager, icon: confirm block 13 | blockmanager -> icon : write_precommit_state 14 | end 15 | 16 | group confirm block every 10sec 17 | blockmanager -> icon : invoke block 18 | icon -> blockmanager : response 19 | note over blockmanager, icon: confirm block 20 | blockmanager -> icon : write_precommit_state 21 | end 22 | 23 | group transaction 24 | 25 | client -> rpcserver: icx_sendTransaction 26 | rpcserver -> icon : validate_transaction 27 | icon -> rpcserver : response 28 | rpcserver -> blockmanager : create_icx_tx 29 | blockmanager -> blockmanager : add TX to block 30 | blockmanager -> rpcserver : response 31 | rpcserver -> client : response 32 | 33 | end 34 | 35 | group query 36 | 37 | client -> rpcserver: icx_getTransactionResult\n icx_getTransactionByHash\n icx_getLastBlock\n icx_getBlockByHash\n icx_getBlockByHeight 38 | rpcserver -> blockmanager : get_invoke_result\nget_tx_info\nget_block 39 | blockmanager -> blockmanager : search 40 | blockmanager -> rpcserver : response 41 | rpcserver -> client : response 42 | 43 | end 44 | 45 | @enduml 46 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | from setuptools import setup, find_packages 4 | 5 | with open('requirements.txt') as requirements: 6 | requires = list(requirements) 7 | 8 | version = os.environ.get('VERSION') 9 | if version is None: 10 | with open(os.path.join('.', 'VERSION')) as version_file: 11 | version = version_file.read().strip() 12 | 13 | package_data = {'tbears': ['data/mainnet.tar.gz']} 14 | 15 | setup_options = { 16 | 'name': 'tbears', 17 | 'version': version, 18 | 'description': 'Test suite for ICON SCORE development', 19 | 'long_description': open('README.md').read(), 20 | 'long_description_content_type': "text/markdown", 21 | 'url': 'https://github.com/icon-project/t-bears', 22 | 'author': 'ICON Foundation', 23 | 'author_email': 'foo@icon.foundation', 24 | 'packages': find_packages(exclude=['tests*', 'docs']), 25 | 'package_data': package_data, 26 | 'include_package_data': True, 27 | 'py_modules': ['tbears'], 28 | 'license': "Apache License 2.0", 29 | 'install_requires': requires, 30 | 'test_suite': 'tests', 31 | 'python_requires': '>=3.7, <3.8', 32 | 'entry_points': { 33 | 'console_scripts': [ 34 | 'tbears=tbears:main' 35 | ], 36 | }, 37 | 'classifiers': [ 38 | 'Development Status :: 5 - Production/Stable', 39 | 'Intended Audience :: Developers', 40 | 'Intended Audience :: System Administrators', 41 | 'Natural Language :: English', 42 | 'License :: OSI Approved :: Apache Software License', 43 | 'Programming Language :: Python :: 3.7' 44 | ] 45 | } 46 | 47 | setup(**setup_options) 48 | -------------------------------------------------------------------------------- /tests/test_util/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright 2017-2018 ICON Foundation 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | import json 16 | import os 17 | import zipfile 18 | 19 | from tbears.util import PROJECT_ROOT_PATH 20 | 21 | TEST_DIRECTORY = os.path.abspath(os.path.join(PROJECT_ROOT_PATH, 'tests')) 22 | TEST_UTIL_DIRECTORY = os.path.join(TEST_DIRECTORY, 'test_util') 23 | IN_MEMORY_ZIP_TEST_DIRECTORY = os.path.join(TEST_UTIL_DIRECTORY, 'test_in_memory_zip') 24 | 25 | 26 | def get_total_supply(path: str): 27 | # get total supply using config file. 28 | with open(path) as config_file: 29 | conf = json.load(config_file) 30 | 31 | account_array = conf['genesis']['accounts'] 32 | balance_info = [int(elem['balance'], 16) for elem in account_array] 33 | total_supply = sum(balance_info) 34 | 35 | return hex(total_supply) 36 | 37 | 38 | def zip_dir(path: str): 39 | zipf = zipfile.ZipFile(f'{path}.zip', 'w', zipfile.ZIP_DEFLATED) 40 | 41 | for root, dirs, files in os.walk(path): 42 | for file in files: 43 | zipf.write(os.path.join(root, file)) 44 | 45 | zipf.close() 46 | -------------------------------------------------------------------------------- /tbears/block_manager/tx_verifier.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 ICON Foundation 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import hashlib 16 | 17 | from iconcommons import Logger 18 | from coincurve import PublicKey 19 | 20 | 21 | def address_from_pubkey(pubkey: bytes): 22 | hash_pub = hashlib.sha3_256(pubkey[1:]).hexdigest() 23 | return f"hx{hash_pub[-40:]}" 24 | 25 | 26 | def verify_signature(msg_hash: bytes, signature: bytes, sender: str) -> bool: 27 | if isinstance(msg_hash, bytes) \ 28 | and len(msg_hash) == 32 \ 29 | and isinstance(signature, bytes) \ 30 | and len(signature) == 65: 31 | 32 | public_key = PublicKey.from_signature_and_message( 33 | serialized_sig=signature, 34 | message=msg_hash, 35 | hasher=None 36 | ) 37 | 38 | address: str = address_from_pubkey(public_key.format(compressed=False)) 39 | if address == sender: 40 | return True 41 | 42 | Logger.info(f'Expected address={sender}', "verify_signature") 43 | Logger.info(f'Signed address={address}', "verify_signature") 44 | 45 | return False 46 | -------------------------------------------------------------------------------- /tbears/tools/mainnet/tbears_server_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "hostAddress": "127.0.0.1", 3 | "port": 9000, 4 | "scoreRootPath": "./.score", 5 | "stateDbRootPath": "./.statedb", 6 | "log": { 7 | "logger": "tbears", 8 | "level": "info", 9 | "filePath": "./tbears.log", 10 | "colorLog": true, 11 | "outputType": "file", 12 | "rotate": { 13 | "type": "bytes", 14 | "maxBytes": 10485760, 15 | "backupCount": 10 16 | } 17 | }, 18 | "service": { 19 | "fee": false, 20 | "audit": false, 21 | "deployerWhiteList": false 22 | }, 23 | "builtinScoreOwner": "hxe7af5fcfd8dfc67530a01a0e403882687528dfcb", 24 | "genesis": { 25 | "nid": "0x3", 26 | "accounts": [ 27 | { 28 | "name": "genesis", 29 | "address": "hx0000000000000000000000000000000000000000", 30 | "balance": "0x2961fff8ca4a62327800000" 31 | }, 32 | { 33 | "name": "fee_treasury", 34 | "address": "hx1000000000000000000000000000000000000000", 35 | "balance": "0x0" 36 | }, 37 | { 38 | "name": "test1", 39 | "address": "hxe7af5fcfd8dfc67530a01a0e403882687528dfcb", 40 | "balance": "0x2961fff8ca4a62327800000" 41 | } 42 | ] 43 | }, 44 | "blockConfirmInterval": 1, 45 | "blockConfirmEmpty": true, 46 | "blockGeneratorRotation": true, 47 | "blockGenerateCountPerLeader": 10, 48 | "blockManualConfirm": false, 49 | "networkDelayMs": 500, 50 | "iissCalculatePeriod": 5, 51 | "termPeriod": 5, 52 | "mainPRepCount": 4 53 | } 54 | -------------------------------------------------------------------------------- /tests/test_keystore.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright 2017-2018 ICON Foundation 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | import os 17 | import unittest 18 | 19 | from iconsdk.exception import KeyStoreException 20 | from iconsdk.wallet.wallet import KeyWallet 21 | from iconsdk.utils.convert_type import convert_hex_str_to_bytes 22 | 23 | 24 | def key_from_key_store(file_path, password): 25 | wallet = KeyWallet.load(file_path, password) 26 | return convert_hex_str_to_bytes(wallet.get_private_key()) 27 | 28 | 29 | class TestKeyStore(unittest.TestCase): 30 | def test_private_key(self): 31 | path = 'keystoretest' 32 | password = 'qwer1234%' 33 | 34 | content = KeyWallet.create() 35 | content.store(path, password) 36 | 37 | # get private key from keystore file 38 | written_key = key_from_key_store(file_path=path, password=password) 39 | self.assertTrue(isinstance(written_key, bytes)) 40 | 41 | # wrong password 42 | self.assertRaises(KeyStoreException, key_from_key_store, path, 'wrongpasswd') 43 | 44 | # wrong path 45 | self.assertRaises(KeyStoreException, key_from_key_store, 'wrongpath', password) 46 | os.remove(path) -------------------------------------------------------------------------------- /tests/test_util/test_icon_config/test_tbears_server_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "hostAddress": "0.0.0.0_config_path", 3 | "port": 9000, 4 | "scoreRootPath": "./.score_config_path", 5 | "stateDbRootPath": "./.statedb_config_path", 6 | "log": { 7 | "logger": "tbears_config_path", 8 | "level": "info_config_path", 9 | "filePath": "./tbears.log_config_path", 10 | "colorLog": true, 11 | "outputType": "console|file_config_path", 12 | "rotate": { 13 | "type": "bytes_config", 14 | "maxBytes": 10485760, 15 | "backupCount": 10 16 | } 17 | }, 18 | "service": { 19 | "fee": false, 20 | "audit": false, 21 | "deployerWhiteList": false 22 | }, 23 | "genesis": { 24 | "nid": "0x03_config_path", 25 | "accounts": [ 26 | { 27 | "name": "genesis", 28 | "address": "hx0000000000000000000000000000000000000000", 29 | "balance": "0x2961fff8ca4a62327800000_config_path" 30 | }, 31 | { 32 | "name": "fee_treasury_config_path", 33 | "address": "hx1000000000000000000000000000000000000000", 34 | "balance": "0x0_config_path" 35 | }, 36 | { 37 | "name": "test1", 38 | "address": "hxe7af5fcfd8dfc67530a01a0e403882687528dfcb", 39 | "balance": "0x2961fff8ca4a62327800000" 40 | } 41 | ] 42 | }, 43 | "channel": "loopchain_default_config_path", 44 | "amqpKey": "7100_config_path", 45 | "amqpTarget": "127.0.0.1_config_path", 46 | "blockConfirmInterval": 10, 47 | "blockConfirmEmpty": true, 48 | "blockGeneratorRotation": true, 49 | "blockGenerateCountPerLeader": 10 50 | } -------------------------------------------------------------------------------- /tbears/block_manager/icon_service.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 ICON Foundation 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from typing import TYPE_CHECKING 16 | 17 | from earlgrey import MessageQueueStub, message_queue_task 18 | from iconcommons.logger import Logger 19 | 20 | 21 | if TYPE_CHECKING: 22 | from earlgrey import RobustConnection 23 | 24 | 25 | class IconScoreInnerTask(object): 26 | """ 27 | Send request to 'iconscore' message queue 28 | """ 29 | @message_queue_task 30 | async def hello(self): 31 | pass 32 | 33 | @message_queue_task 34 | async def invoke(self, request: dict) -> dict: 35 | pass 36 | 37 | @message_queue_task 38 | async def query(self, request: dict) -> dict: 39 | pass 40 | 41 | @message_queue_task 42 | async def write_precommit_state(self, request: dict) -> dict: 43 | pass 44 | 45 | @message_queue_task 46 | async def remove_precommit_state(self, request: dict) -> dict: 47 | pass 48 | 49 | @message_queue_task 50 | async def call(self, request: dict) -> dict: 51 | pass 52 | 53 | 54 | class IconStub(MessageQueueStub[IconScoreInnerTask]): 55 | TaskType = IconScoreInnerTask 56 | 57 | def _callback_connection_close(self, connection: 'RobustConnection'): 58 | Logger.error(f'[IconStub] close message queue connection', 'tbears_block_manager') 59 | self._task._block_manager.close() 60 | -------------------------------------------------------------------------------- /tbears/libs/scoretest/mock/icx_engine.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright 2018 ICON Foundation 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | from typing import TYPE_CHECKING 17 | 18 | from iconservice.base.exception import InvalidRequestException 19 | 20 | from .db import PlyvelDB 21 | 22 | if TYPE_CHECKING: 23 | from iconservice.base.address import Address 24 | from iconservice.iconscore.icon_score_context import IconScoreContext 25 | 26 | 27 | class IcxEngine: 28 | db: PlyvelDB = None 29 | 30 | @classmethod 31 | def get_balance(cls, context: 'IconScoreContext', address: 'Address') -> int: 32 | balance = none_to_zero(cls.db.get(context, address.to_bytes())) 33 | return balance 34 | 35 | @classmethod 36 | def transfer(cls, context: 'IconScoreContext', _from: 'Address', _to: 'Address', amount: int): 37 | sender_address = _from.to_bytes() 38 | receiver_address = _to.to_bytes() 39 | 40 | sender_balance = none_to_zero(cls.db.get(context, sender_address)) 41 | receiver_balance = none_to_zero(cls.db.get(context, receiver_address)) 42 | 43 | if sender_balance < amount: 44 | raise InvalidRequestException('out of balance') 45 | 46 | sender_balance -= amount 47 | receiver_balance += amount 48 | 49 | cls.db._db[sender_address] = sender_balance 50 | cls.db._db[receiver_address] = receiver_balance 51 | 52 | 53 | def none_to_zero(balance) -> int: 54 | if balance is None: 55 | return 0 56 | return balance 57 | -------------------------------------------------------------------------------- /tbears/libs/scoretest/mock/db.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright 2018 ICON Foundation 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | from iconservice.base.address import Address 18 | from iconservice.base.exception import DatabaseException 19 | from iconservice.database.db import KeyValueDatabase, _is_db_writable_on_context 20 | 21 | value_type = (int, str, bytes, bool, Address, None) 22 | 23 | 24 | class MockKeyValueDatabase(KeyValueDatabase): 25 | """plyvel database wrapper""" 26 | 27 | @staticmethod 28 | def create_db() -> 'PlyvelDB': 29 | db = PlyvelDB(PlyvelDB.make_db()) 30 | return db 31 | 32 | 33 | class PlyvelDB(object): 34 | """plyvel database wrapper""" 35 | 36 | @staticmethod 37 | def make_db() -> dict: 38 | return dict() 39 | 40 | def __init__(self, db: dict) -> None: 41 | self._db = db 42 | 43 | def get(self, context, bytes_key: bytes) -> value_type: 44 | return self._db.get(bytes_key) 45 | 46 | def put(self, context, bytes_key: bytes, value: value_type) -> None: 47 | if not _is_db_writable_on_context(context): 48 | raise DatabaseException('put is not allowed') 49 | 50 | self._db[bytes_key] = value 51 | 52 | def delete(self, context, bytes_key: bytes) -> None: 53 | if not _is_db_writable_on_context(context): 54 | raise DatabaseException('delete is not allowed') 55 | 56 | if bytes_key in self._db: 57 | del self._db[bytes_key] 58 | 59 | def close(self) -> None: 60 | self._db.clear() 61 | 62 | def get_sub_db(self, key: bytes): 63 | return PlyvelDB(self.make_db()) 64 | 65 | def iterator(self) -> iter: 66 | return iter(self._db) 67 | 68 | def prefixed_db(self, bytes_prefix) -> 'PlyvelDB': 69 | return PlyvelDB(PlyvelDB.make_db()) 70 | -------------------------------------------------------------------------------- /tbears/block_manager/tbears_db.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 ICON Foundation 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | import os 15 | 16 | import plyvel 17 | 18 | 19 | class TbearsDB: 20 | @staticmethod 21 | def make_db(path: str, create_if_missing: bool = True) -> plyvel.DB: 22 | if not os.path.exists(path): 23 | os.makedirs(path) 24 | return plyvel.DB(path, create_if_missing=create_if_missing) 25 | 26 | def __init__(self, db: plyvel.DB) -> None: 27 | """Constructor 28 | 29 | :param db: plyvel DB instance 30 | """ 31 | self._db = db 32 | 33 | def get(self, key: bytes) -> bytes: 34 | """Get value from db using key 35 | 36 | :param key: db key 37 | :return: value indicated by key otherwise None 38 | """ 39 | return self._db.get(key) 40 | 41 | def put(self, key: bytes, value: bytes) -> None: 42 | """Put value into db using key. 43 | 44 | :param key: (bytes): db key 45 | :param value: (bytes): data to be stored in db 46 | """ 47 | self._db.put(key, value) 48 | 49 | def delete(self, key: bytes) -> None: 50 | """Delete a row 51 | 52 | :param key: delete the row indicated by key. 53 | """ 54 | self._db.delete(key) 55 | 56 | def close(self) -> None: 57 | """Close db 58 | """ 59 | if self._db: 60 | self._db.close() 61 | self._db = None 62 | 63 | def create_write_batch(self) -> object: 64 | return self._db.write_batch(transaction=True) 65 | 66 | @staticmethod 67 | def write_batch(write_batch, key: bytes, value: bytes): 68 | write_batch.put(key, value) 69 | 70 | @staticmethod 71 | def commit_write_batch(write_batch): 72 | write_batch.write() 73 | 74 | def iterator(self) -> iter: 75 | return self._db.iterator() 76 | -------------------------------------------------------------------------------- /Docker/README.md: -------------------------------------------------------------------------------- 1 | # Dockerfile for T-Bears Container 2 | 3 | This document describes how to build a T-Bears Docker image and run the image as a Docker container. 4 | 5 | ## Requirements 6 | * [Docker](https://docs.docker.com/) 7 | 8 | ## Base Docker Image 9 | 10 | * python:3.7.3-slim-stretch 11 | 12 | ## Build T-Bears Docker Image 13 | ``` 14 | $ make build 15 | ``` 16 | 17 | Note that you don't need to build the Docker image by yourself. 18 | The official Docker images for T-Bears are available from the Docker Hub: [https://hub.docker.com/r/iconloop/tbears](https://hub.docker.com/r/iconloop/tbears). 19 | You can download the T-Bears Docker image by using the `docker pull` command. 20 | 21 | ``` 22 | $ docker pull iconloop/tbears:mainnet 23 | ``` 24 | 25 | The `mainnet` tag has been attached to the Docker image that is same with the ICON Mainnet environment. 26 | 27 | ## Usage 28 | 29 | ### Running T-Bears Docker Container 30 | 31 | ``` 32 | $ make run 33 | ``` 34 | 35 | or 36 | 37 | ``` 38 | $ docker run -it -p 9000:9000 iconloop/tbears:mainnet 39 | ``` 40 | 41 | This will start the T-Bears container that is listening on port 9000 for incoming requests. 42 | If you want the T-Bears container to listen on a different port, replace `${LISTEN_PORT}` with your desired port number. 43 | 44 | ``` 45 | $ docker run -it -p ${LISTEN_PORT}:9000 iconloop/tbears:mainnet 46 | ``` 47 | 48 | ### Test with T-Bears Docker Container 49 | 50 | #### From the Container 51 | In the same terminal, run the following T-Bears CLI command to see if the T-Bears service is working correctly. 52 | ```bash 53 | root@27cadb5e0047:/work# tbears totalsupply 54 | Total supply of ICX in hex: 0x52c3fff19494c464f000000 55 | Total supply of ICX in decimal: 1600920000000000000000000000 56 | ``` 57 | 58 | #### From the Host System 59 | From another terminal, run the following T-Bears CLI command to see if it could be correctly connected to the T-Bears service. 60 | 61 | If you modified the listening port of T-Bears container, run the T-Bears CLI command with `-u` option. 62 | 63 | ```bash 64 | (.venv) /work $ tbears totalsupply -u http://127.0.0.1:9000/api/v3 65 | Total supply of ICX in hex: 0x52c3fff19494c464f000000 66 | Total supply of ICX in decimal: 1600920000000000000000000000 67 | ``` 68 | Note that you don't need to install RabbitMQ package on the host system in this configuration. Just need to install T-Bears package for issuing some CLI commands. 69 | 70 | 71 | ## License 72 | 73 | This project follows the Apache 2.0 License. Please refer to [LICENSE](https://www.apache.org/licenses/LICENSE-2.0) for details. 74 | -------------------------------------------------------------------------------- /tests/test_keystore_manager.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright 2017-2018 ICON Foundation 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | import os 17 | import unittest 18 | import json 19 | 20 | from iconsdk.wallet.wallet import KeyWallet 21 | from iconsdk.utils.convert_type import convert_hex_str_to_bytes 22 | 23 | from tbears.command.command_wallet import CommandWallet 24 | from tbears.tbears_exception import TBearsCommandException 25 | from tbears.command.command import Command 26 | 27 | 28 | class TestKeyStoreManager(unittest.TestCase): 29 | def setUp(self): 30 | self.cmd = Command() 31 | self.parser = self.cmd.parser 32 | self.keystore_path = 'unit_test_keystore' 33 | self.keystore_password = 'qwer1234%' 34 | 35 | def tearDown(self): 36 | if os.path.isfile(self.keystore_path): 37 | os.remove(self.keystore_path) 38 | 39 | def test_make_key_store_content(self): 40 | # make keystore file 41 | content = KeyWallet.create() 42 | content.store(self.keystore_path, self.keystore_password) 43 | 44 | # get private key from keystore file 45 | # written_key = key_from_key_store(file_path=self.keystore_path, password=self.keystore_password) 46 | 47 | written_key = convert_hex_str_to_bytes( 48 | KeyWallet 49 | .load(self.keystore_path,self.keystore_password) 50 | .get_private_key()) 51 | 52 | self.assertTrue(isinstance(written_key, bytes)) 53 | 54 | os.remove(self.keystore_path) 55 | 56 | def test_validate_password(self): 57 | # Invalid password (password length is more than 8) 58 | invalid_password = 'qwe123!' 59 | self.assertRaises(TBearsCommandException, CommandWallet._check_keystore, invalid_password) 60 | 61 | # Invalid password (password has to be combined with special character and alphabet and number) 62 | invalid_password = 'qwer12345' 63 | self.assertRaises(TBearsCommandException, CommandWallet._check_keystore, invalid_password) 64 | -------------------------------------------------------------------------------- /tests/test_parsing_server.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 ICON Foundation 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import os 16 | 17 | from tests.test_parsing_command import TestCommand 18 | from tests.test_util import TEST_UTIL_DIRECTORY 19 | 20 | class TestCommandServer(TestCommand): 21 | def tearDown(self): 22 | pass 23 | 24 | # Test if cli arguments are parced correctly. 25 | def test_start_args_parsing(self): 26 | hostAddress = '9.9.9.9' 27 | port = 5000 28 | config_path = os.path.join(TEST_UTIL_DIRECTORY, 'test_tbears_server_config.json') 29 | 30 | # Parsing test 31 | cmd = f'start -a {hostAddress} -p {port} -c {config_path}' 32 | parsed = self.parser.parse_args(cmd.split()) 33 | self.assertEqual(parsed.command, 'start') 34 | self.assertEqual(str(parsed.hostAddress), hostAddress) 35 | self.assertEqual(int(parsed.port), port) 36 | self.assertEqual(parsed.config, config_path) 37 | 38 | # Too many arguments (start cli doesn't need argument) 39 | cmd = f'start wrongArgument' 40 | self.assertRaises(SystemExit, self.parser.parse_args, cmd.split()) 41 | 42 | # Wrong option 43 | cmd = f'start -w wrongOption' 44 | self.assertRaises(SystemExit, self.parser.parse_args, cmd.split()) 45 | 46 | # Invalid --address type 47 | cmd = f'start --address type_is_to_be_ip_address' 48 | self.assertRaises(SystemExit, self.parser.parse_args, cmd.split()) 49 | 50 | # Invalid --port type 51 | cmd = f'start --port type_to_be_int' 52 | self.assertRaises(SystemExit, self.parser.parse_args, cmd.split()) 53 | 54 | def test_stop_args_parsing(self): 55 | # Parsing test 56 | cmd = f'stop' 57 | parsed = self.parser.parse_args(cmd.split()) 58 | self.assertEqual(parsed.command, 'stop') 59 | 60 | # Invalid argument (stop cli doesn't accept argument) 61 | cmd = f'stop wrongArgument' 62 | self.assertRaises(SystemExit, self.parser.parse_args, cmd.split()) 63 | 64 | # Wrong option (stop cli has no option) 65 | cmd = f'stop -w wrongOption' 66 | self.assertRaises(SystemExit, self.parser.parse_args, cmd.split()) 67 | -------------------------------------------------------------------------------- /tests/test_prep_manager.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright 2017-2018 ICON Foundation 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | import unittest 16 | 17 | from tbears.block_manager.block_manager import PRepManager 18 | from tbears.config.tbears_config import keystore_test1 19 | 20 | 21 | PREP_LIST = [ 22 | { 23 | "id": "hx86aba2210918a9b116973f3c4b27c41a54d5dafe", 24 | "publicKey": "04a69f73cca23a9ac5c8b567dc185a756e97c982164fe25859e0d1dcc1475c80a615b2123af1f5f94c11e3e9402c3ac558f500199d95b6d3e301758586281dcd26", 25 | "p2pEndPoint": "target://123.45.67.89:7100" 26 | }, 27 | { 28 | "id": "hx13aca3210918a9b116973f3c4b27c41a54d5dad1", 29 | "publicKey": "0483ae642ca89c9ac5c8b567dc185a756e97c982164fe25859e0d1dcc1475c80a615b2123af1f5f94c11e3e9402c3ac558f500199d95b6d3e301758586281e3a27", 30 | "p2pEndPoint": "target://210.34.56.17:7100" 31 | } 32 | ] 33 | 34 | 35 | class TestTBearsPRepManager(unittest.TestCase): 36 | 37 | def setUp(self): 38 | pass 39 | 40 | def tearDown(self): 41 | pass 42 | 43 | def test_get_prev_block_contributors_info(self): 44 | # There is no P-Reps 45 | manager = PRepManager(is_generator_rotation=True, gen_count_per_leader=1) 46 | info = manager.get_prev_block_contributors_info() 47 | self.assertEqual(keystore_test1.get('address'), info.get('prevBlockGenerator')) 48 | self.assertEqual(0, len(info.get('prevBlockValidators'))) 49 | 50 | # There is 2 P-Reps 51 | manager = PRepManager(is_generator_rotation=True, gen_count_per_leader=1, prep_list=PREP_LIST) 52 | info = manager.get_prev_block_contributors_info() 53 | self.assertEqual(PREP_LIST[0].get('id'), info.get('prevBlockGenerator')) 54 | self.assertEqual(len(PREP_LIST) - 1, len(info.get('prevBlockValidators'))) 55 | self.assertEqual(PREP_LIST[1].get('id'), info.get('prevBlockValidators')[0]) 56 | 57 | # after rotate 58 | info = manager.get_prev_block_contributors_info() 59 | self.assertEqual(PREP_LIST[1].get('id'), info.get('prevBlockGenerator')) 60 | self.assertEqual(len(PREP_LIST) - 1, len(info.get('prevBlockValidators'))) 61 | self.assertEqual(PREP_LIST[0].get('id'), info.get('prevBlockValidators')[0]) 62 | -------------------------------------------------------------------------------- /tests/test_tbears_db.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright 2017-2018 ICON Foundation 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | import os 17 | import shutil 18 | import unittest 19 | 20 | from tbears.block_manager.tbears_db import TbearsDB 21 | 22 | DIRECTORY_PATH = os.path.abspath((os.path.dirname(__file__))) 23 | DB_PATH = os.path.join(DIRECTORY_PATH, './.tbears_db') 24 | 25 | 26 | class TestTBearsDB(unittest.TestCase): 27 | 28 | def setUp(self): 29 | self.TBEARS_DB = TbearsDB(TbearsDB.make_db(DB_PATH)) 30 | self.test_key = b'test_key' 31 | self.test_value = b'test_value' 32 | 33 | def tearDown(self): 34 | self.TBEARS_DB.close() 35 | shutil.rmtree(DB_PATH) 36 | 37 | def test_put_and_get(self): 38 | # Put and get 39 | self.TBEARS_DB.put(self.test_key, self.test_value) 40 | ret = self.TBEARS_DB.get(self.test_key) 41 | self.assertEqual(ret, self.test_value) 42 | 43 | # overwrite 44 | overwrite_value = b'test_value_overwrite' 45 | self.TBEARS_DB.put(self.test_key, overwrite_value) 46 | ret = self.TBEARS_DB.get(self.test_key) 47 | self.assertEqual(ret, overwrite_value) 48 | 49 | # get invalid key 50 | ret = self.TBEARS_DB.get(b'invalid_key') 51 | self.assertIsNone(ret) 52 | 53 | # put invalid type 54 | self.assertRaises(TypeError, self.TBEARS_DB.put, 'test_key', self.test_value) 55 | self.assertRaises(TypeError, self.TBEARS_DB.put, self.test_key, 123) 56 | 57 | def test_delete(self): 58 | self.TBEARS_DB.put(self.test_key, self.test_value) 59 | ret = self.TBEARS_DB.get(self.test_key) 60 | self.assertEqual(ret, self.test_value) 61 | self.TBEARS_DB.delete(self.test_key) 62 | ret = self.TBEARS_DB.get(self.test_key) 63 | self.assertIsNone(ret) 64 | 65 | def test_iterator(self): 66 | self.TBEARS_DB.put(b'key1', b'value1') 67 | self.TBEARS_DB.put(b'key2', b'value2') 68 | self.TBEARS_DB.put(b'key3', b'value3') 69 | self.TBEARS_DB.put(b'key4', b'value4') 70 | i = 1 71 | 72 | for _, actual_value in self.TBEARS_DB.iterator(): 73 | expected_value = ('value' + str(i)).encode() 74 | self.assertEqual(expected_value, actual_value) 75 | i += 1 76 | -------------------------------------------------------------------------------- /tests/test_score_test_command.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright 2019 ICON Foundation 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | import unittest 17 | from typing import Optional 18 | 19 | from iconservice.base.block import Block 20 | from iconservice.icon_service_engine import IconServiceEngine 21 | 22 | from tbears.libs.icon_integrate_test import IconIntegrateTestBase, create_block_hash, create_timestamp 23 | 24 | 25 | class TestScoreTestCommand(unittest.TestCase): 26 | # since supporting new block hash, commit and rollback method in icon service engine has changed. 27 | # so tests about compatibility. 28 | def test_instant_block_hash_compatibility(self): 29 | # success case: when icon service version is under 1.3.0 (these versions only get instant block hash) 30 | 31 | # define mocked commit method 32 | def prev_commit(obj, block: 'Block'): 33 | assert isinstance(obj, IconServiceEngine) 34 | assert isinstance(block, Block) 35 | IconServiceEngine.commit = prev_commit 36 | 37 | icon_integrate_test_base = IconIntegrateTestBase() 38 | icon_integrate_test_base.setUpClass() 39 | icon_integrate_test_base.setUp() 40 | 41 | instant_block = Block(block_height=1, 42 | block_hash=create_block_hash(), 43 | timestamp=create_timestamp(), 44 | prev_hash=create_block_hash(), 45 | cumulative_fee=0) 46 | icon_integrate_test_base._write_precommit_state(instant_block) 47 | icon_integrate_test_base.tearDown() 48 | 49 | # success case: when icon service version is 1.3.0 and more 50 | # (these versions get instant block hash and new block hash) 51 | 52 | # define mocked commit method 53 | def new_commit(obj, block_height: int, instant_block_hash: bytes, block_hash: Optional[bytes]): 54 | assert isinstance(obj, IconServiceEngine) 55 | assert isinstance(block_height, int) 56 | assert isinstance(instant_block_hash, bytes) 57 | assert isinstance(block_hash, bytes) 58 | 59 | IconServiceEngine.commit = new_commit 60 | icon_integrate_test_base = IconIntegrateTestBase() 61 | icon_integrate_test_base.setUpClass() 62 | icon_integrate_test_base.setUp() 63 | 64 | instant_block = Block(block_height=1, 65 | block_hash=create_block_hash(), 66 | timestamp=create_timestamp(), 67 | prev_hash=create_block_hash(), 68 | cumulative_fee=0) 69 | icon_integrate_test_base._write_precommit_state(instant_block) 70 | icon_integrate_test_base.tearDown() 71 | -------------------------------------------------------------------------------- /tbears/block_manager/task.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 ICON Foundation 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | import time 15 | import asyncio 16 | from contextlib import suppress 17 | 18 | 19 | class Periodic: 20 | """ 21 | Class for periodic work in asyncio 22 | """ 23 | def __init__(self, func: callable, interval: int): 24 | self.func = func 25 | self.interval = interval 26 | self.is_started = False 27 | self._task = None 28 | 29 | async def start(self): 30 | """ 31 | Start the periodic work 32 | :return: 33 | """ 34 | if not self.is_started: 35 | self.is_started = True 36 | # Start task to call func periodically: 37 | self._task = asyncio.ensure_future(self._run()) 38 | 39 | async def stop(self): 40 | """ 41 | Stop the periodic work 42 | :return: 43 | """ 44 | if self.is_started: 45 | self.is_started = False 46 | # Stop task and await it stopped: 47 | self._task.cancel() 48 | with suppress(asyncio.CancelledError): 49 | await self._task 50 | 51 | async def _run(self): 52 | """ 53 | Do the work 54 | :return: 55 | """ 56 | next_time = time.time() + self.interval 57 | while True: 58 | # get time to sleep 59 | remain_time = next_time - time.time() 60 | if remain_time > 0: 61 | await asyncio.sleep(remain_time) 62 | 63 | # set next working time 64 | next_time = time.time() + self.interval 65 | 66 | # do work 67 | await self.func() 68 | 69 | 70 | class Immediate: 71 | """ 72 | Class for immediate work in asyncio 73 | """ 74 | def __init__(self): 75 | self.funcs: list = [] 76 | self.is_started = False 77 | self._task = None 78 | 79 | async def start(self): 80 | """ 81 | Start the immediate work 82 | :return: 83 | """ 84 | if not self.is_started: 85 | self.is_started = True 86 | # Start task to call func periodically: 87 | self._task = asyncio.ensure_future(self._run()) 88 | 89 | async def stop(self): 90 | """ 91 | Stop the immediate work 92 | :return: 93 | """ 94 | if self.is_started: 95 | self.is_started = False 96 | # Stop task and await it stopped: 97 | self._task.cancel() 98 | with suppress(asyncio.CancelledError): 99 | await self._task 100 | 101 | def add_func(self, func: callable): 102 | self.funcs.append(func) 103 | 104 | async def _run(self): 105 | """ 106 | Do the work 107 | :return: 108 | """ 109 | while True: 110 | if self.funcs: 111 | func: callable = self.funcs.pop() 112 | await func() 113 | else: 114 | await asyncio.sleep(0) 115 | -------------------------------------------------------------------------------- /tbears/tbears_exception.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright 2018 ICON Foundation 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | from enum import IntEnum, unique 16 | from typing import Optional 17 | 18 | 19 | @unique 20 | class TBearsExceptionCode(IntEnum): 21 | """Result code enumeration 22 | 23 | Refer to http://www.simple-is-better.org/json-rpc/jsonrpc20.html#examples 24 | """ 25 | OK = 0 26 | COMMAND_ERROR = 1 27 | WRITE_FILE_ERROR = 2 28 | DELETE_TREE_ERROR = 3 29 | KEY_STORE_ERROR = 5 30 | DEPLOY_PAYLOAD_ERROR = 6 31 | ICON_CLIENT_ERROR = 7 32 | ZIP_MEMORY = 8 33 | ESTIMATE = 9 34 | 35 | def __str__(self) -> str: 36 | return str(self.name).capitalize().replace('_', ' ') 37 | 38 | 39 | class TBearsBaseException(BaseException): 40 | 41 | def __init__(self, message: Optional[str], code: TBearsExceptionCode = TBearsExceptionCode.OK): 42 | if message is None: 43 | message = str(code) 44 | self.__message = message 45 | self.__code = code 46 | 47 | @property 48 | def message(self): 49 | return self.__message 50 | 51 | @property 52 | def code(self): 53 | return self.__code 54 | 55 | def __str__(self): 56 | return f'{self.message}' 57 | 58 | 59 | class TBearsWriteFileException(TBearsBaseException): 60 | """Error while write file.""" 61 | def __init__(self, message: Optional[str]): 62 | super().__init__(message, TBearsExceptionCode.WRITE_FILE_ERROR) 63 | 64 | 65 | class TBearsDeleteTreeException(TBearsBaseException): 66 | """Error while delete file.""" 67 | def __init__(self, message: Optional[str]): 68 | super().__init__(message, TBearsExceptionCode.DELETE_TREE_ERROR) 69 | 70 | 71 | class KeyStoreException(TBearsBaseException): 72 | """"Error while make or load keystore file """ 73 | def __init__(self, message: Optional[str]): 74 | super().__init__(message, TBearsExceptionCode.KEY_STORE_ERROR) 75 | 76 | 77 | class ZipException(TBearsBaseException): 78 | """"Error while write zip in memory""" 79 | def __init__(self, message: Optional[str]): 80 | super().__init__(message, TBearsExceptionCode.ZIP_MEMORY) 81 | 82 | 83 | class DeployPayloadException(TBearsBaseException): 84 | """Error while fill deploy payload""" 85 | def __init__(self, message: Optional[str]): 86 | super().__init__(message, TBearsExceptionCode.DEPLOY_PAYLOAD_ERROR) 87 | 88 | 89 | class IconClientException(TBearsBaseException): 90 | """Error while send request""" 91 | def __init__(self, message: Optional[str]): 92 | super().__init__(message, TBearsExceptionCode.ICON_CLIENT_ERROR) 93 | 94 | 95 | class JsonContentsException(TBearsBaseException): 96 | """Error when initialize JsonContent object""" 97 | def __init__(self, message: Optional[str]): 98 | super().__init__(message, TBearsExceptionCode.JSONRPC_CONTENT_ERROR) 99 | 100 | 101 | class TBearsCommandException(TBearsBaseException): 102 | """Error when tbears cli options are wrong""" 103 | def __init__(self, message: Optional[str]): 104 | super().__init__(message, TBearsExceptionCode.COMMAND_ERROR) 105 | 106 | 107 | class TBearsEstimateException(TBearsBaseException): 108 | """Error while estimating step""" 109 | def __init__(self, message: Optional[str]): 110 | super().__init__(message, TBearsExceptionCode.COMMAND_ERROR) 111 | -------------------------------------------------------------------------------- /tbears/util/argparse_type.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright 2018 ICON Foundation 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | import os 17 | from argparse import ArgumentTypeError 18 | 19 | from iconservice.base.address import is_icon_address_valid 20 | 21 | from tbears.util import is_valid_hash 22 | 23 | 24 | class IconPath(str): 25 | def __init__(self, mode: str = 'r'): 26 | self._mode = mode 27 | 28 | def __call__(self, string: str) -> str: 29 | if self._mode == 'r' and not os.path.exists(string): 30 | raise ArgumentTypeError(f"There is no '{string}'") 31 | elif self._mode == 'w' and os.path.exists(string): 32 | raise ArgumentTypeError(f"'{string}' must be empty") 33 | elif self._mode == 'd' and not os.path.isdir(string): 34 | raise ArgumentTypeError(f"There is no directory '{string}'") 35 | 36 | if os.path.isdir(string) and string.endswith('/'): 37 | return string[:-1] 38 | 39 | return string 40 | 41 | 42 | class IconAddress(str): 43 | def __init__(self, prefix: str = 'all'): 44 | self._prefix = prefix 45 | 46 | def __call__(self, string: str) -> str: 47 | # check prefix of given address (string). if not 'cx' or 'hx', raise error 48 | if not is_icon_address_valid(string): 49 | raise ArgumentTypeError(f"Invalid address '{string}'") 50 | 51 | if self._prefix != 'all': 52 | if self._prefix != string[:2]: 53 | raise ArgumentTypeError(f"Invalid address '{string}'. Address must start with '{self._prefix}'") 54 | 55 | return string 56 | 57 | 58 | def hash_type(string: str) -> str: 59 | # check hash's length, prefix, lowercase. 60 | if not is_valid_hash(string): 61 | raise ArgumentTypeError(f"Invalid hash '{string}'") 62 | 63 | return string 64 | 65 | 66 | def port_type(string: str) -> int: 67 | try: 68 | port = int(string, 10) 69 | except ValueError: 70 | raise ArgumentTypeError(f"Invalid integer value '{string}'") 71 | except TypeError as e: 72 | raise ArgumentTypeError(f'Invalid type. {e}') 73 | 74 | if port < 0 or port > 65535: 75 | raise ArgumentTypeError(f"Invalid port '{string}'. Port must be 0 < port < 65536") 76 | 77 | return port 78 | 79 | 80 | def non_negative_num_type(string: str) -> str: 81 | try: 82 | value = int(string, 10) 83 | except ValueError: 84 | try: 85 | value = int(string, 16) 86 | except ValueError: 87 | raise ArgumentTypeError(f"Invalid integer value '{string}'. Hexadecimal and decimal values are allowed") 88 | except TypeError as e: 89 | raise ArgumentTypeError(f'Invalid type. {e}') 90 | 91 | if value < 0: 92 | raise ArgumentTypeError(f"Invalid non-negative number '{value}'") 93 | 94 | return hex(value) 95 | 96 | 97 | def loop(input_value: str) -> int: 98 | try: 99 | value_in_float = float(input_value) 100 | except ValueError: 101 | raise ArgumentTypeError(f'You entered invalid value {input_value}') 102 | if value_in_float != float(int(value_in_float)): 103 | raise ArgumentTypeError(f'You entered invalid value {input_value}') 104 | 105 | value_in_str = input_value.lower() 106 | if value_in_str.find("e") == -1: 107 | return int(value_in_str, 10) 108 | else: 109 | significand, exponent = value_in_str.split("e") 110 | return int(significand) * 10 ** int(exponent) 111 | -------------------------------------------------------------------------------- /tbears/command/command.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright 2018 ICON Foundation 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | import sys 16 | import argparse 17 | 18 | from typing import Optional 19 | 20 | from iconcommons.logger import Logger 21 | 22 | from tbears.command.command_wallet import CommandWallet 23 | from tbears.command.command_server import CommandServer 24 | from tbears.command.command_score import CommandScore 25 | from tbears.command.command_util import CommandUtil 26 | from tbears.config.tbears_config import tbears_server_config 27 | from tbears.tbears_exception import TBearsBaseException, TBearsExceptionCode 28 | from tbears.util import get_tbears_version 29 | 30 | 31 | class TbearsParser(argparse.ArgumentParser): 32 | def error(self, message): 33 | sys.stderr.write(f'error: {message}\n\n') 34 | self.print_help() 35 | sys.exit(2) 36 | 37 | 38 | class Command(object): 39 | def __init__(self): 40 | self.version = get_tbears_version() 41 | self._create_parser() 42 | self.cmdServer = CommandServer(self.subparsers) 43 | self.cmdScore = CommandScore(self.subparsers) 44 | self.cmdUtil = CommandUtil(self.subparsers) 45 | self.cmdWallet = CommandWallet(self.subparsers) 46 | 47 | def _create_parser(self): 48 | parser = TbearsParser(prog='tbears', description=f'tbears v{self.version} arguments') 49 | parser.add_argument('-v', '--verbose', help='Verbose mode', action='store_true') 50 | subparsers = parser.add_subparsers(title='Available commands', metavar='command', 51 | description=f'If you want to see help message of commands, ' 52 | f'use "tbears command -h"') 53 | subparsers.required = True 54 | subparsers.dest = 'command' 55 | 56 | self.parser = parser 57 | self.subparsers = subparsers 58 | 59 | return parser 60 | 61 | def run(self, sys_args) -> Optional: 62 | # sys_args is list of commands splitted by space 63 | # e.g. tbears deploy project -k keystore => ['deploy', 'project', '-k', 'keystore'] 64 | try: 65 | # parse_args return the populated namespace 66 | # e.g. Namespace (command='deploy', config=None, keyStore='keystore' ...) 67 | args = self.parser.parse_args(args=sys_args) 68 | self._init_logger(args=args) 69 | if self.cmdServer.check_command(args.command): 70 | result = self.cmdServer.run(args) 71 | elif self.cmdScore.check_command(args.command): 72 | result = self.cmdScore.run(args) 73 | elif self.cmdUtil.check_command(args.command): 74 | result = self.cmdUtil.run(args) 75 | elif self.cmdWallet.check_command(args.command): 76 | result = self.cmdWallet.run(args) 77 | except TBearsBaseException as e: 78 | print(f"{e}") 79 | return e.code.value 80 | except Exception as e: 81 | print(f"Exception: {e}") 82 | return TBearsExceptionCode.COMMAND_ERROR.value 83 | else: 84 | return result 85 | 86 | def _init_logger(self, args): 87 | from iconcommons.icon_config import IconConfig 88 | 89 | if args.verbose: 90 | conf = IconConfig(None, tbears_server_config) 91 | conf.load() 92 | if 'console' not in conf['log']['outputType']: 93 | conf['log']['outputType'] = conf['log']['outputType'] + "|console" 94 | Logger.load_config(conf) 95 | -------------------------------------------------------------------------------- /tests/simpleScore2/tests/test_z_integrate_simpleScore2.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | 4 | from iconsdk.builder.call_builder import CallBuilder 5 | from iconsdk.builder.transaction_builder import DeployTransactionBuilder, CallTransactionBuilder 6 | from iconsdk.libs.in_memory_zip import gen_deploy_data_content 7 | from iconsdk.signed_transaction import SignedTransaction 8 | 9 | from tbears.libs.icon_integrate_test import IconIntegrateTestBase, SCORE_INSTALL_ADDRESS 10 | 11 | DIR_PATH = os.path.abspath(os.path.dirname(__file__)) 12 | 13 | # We put simpleScore test-cases to test SCORE test framework work properly. 14 | # Prefix 'z' is included to run this test file later than test_unit_simpleScore2 test. 15 | # (Since some functions are patched in SCORE unittest, to make sure that they are properly restored) 16 | 17 | 18 | class TestTest(IconIntegrateTestBase): 19 | TEST_HTTP_ENDPOINT_URI_V3 = "http://127.0.0.1:9000/api/v3" 20 | SIMPLE_SCORE_PATH = os.path.abspath(os.path.join(DIR_PATH, "..", "..", "simpleScore")) 21 | SCORE_PROJECT = os.path.abspath(os.path.join(DIR_PATH, '..')) 22 | 23 | def setUp(self): 24 | super().setUp() 25 | 26 | self.icon_service = None 27 | # if you want to send request to network, uncomment next line and set self.TEST_HTTP_ENDPOINT_URI_V3 28 | # self.icon_service = IconService(HTTPProvider(self.TEST_HTTP_ENDPOINT_URI_V3)) 29 | 30 | # install SCORE 31 | self._score_address1 = self._deploy_score(score_path=self.SIMPLE_SCORE_PATH)['scoreAddress'] 32 | self._score_address = self._deploy_score(params={"score_address": self._score_address1}, 33 | score_path=self.SCORE_PROJECT)['scoreAddress'] 34 | 35 | def _deploy_score(self, to: str = SCORE_INSTALL_ADDRESS, params: dict = {}, score_path: str = "") -> dict: 36 | # Generates an instance of transaction for deploying SCORE. 37 | transaction = DeployTransactionBuilder().from_(self._test1.get_address()).to(to).step_limit( 38 | 100_000_000_000).nid(3).nonce(100).content_type("application/zip").params(params).content( 39 | gen_deploy_data_content(score_path)).build() 40 | 41 | # Returns the signed transaction object having a signature 42 | signed_transaction = SignedTransaction(transaction, self._test1) 43 | 44 | # process the transaction in local 45 | tx_result = self.process_transaction(signed_transaction, self.icon_service) 46 | 47 | self.assertEqual(True, tx_result['status']) 48 | self.assertTrue('scoreAddress' in tx_result) 49 | 50 | return tx_result 51 | 52 | def test_integrate_after_unit_tests(self): 53 | call = CallBuilder().from_(self._test1.get_address()).to(self._score_address).method("getSCOREValue").build() 54 | response = self.process_call(call, self.icon_service) 55 | self.assertEqual(response, "") 56 | 57 | new_value = "new_value" 58 | call_tx = CallTransactionBuilder().from_(self._test1.get_address()).to(self._score_address).\ 59 | method("setSCOREValue").params({"value": new_value}).step_limit(10000000).build() 60 | signed_tx = SignedTransaction(call_tx, self._test1) 61 | tx_result = self.process_transaction(signed_tx, self.icon_service) 62 | self.assertTrue(tx_result['status']) 63 | 64 | call = CallBuilder().from_(self._test1.get_address()).to(self._score_address).method("getSCOREValue").build() 65 | response = self.process_call(call, self.icon_service) 66 | self.assertEqual(new_value, response) 67 | 68 | call = CallBuilder().from_(self._test1.get_address()).to(self._score_address).method("get_owner").build() 69 | response = self.process_call(call, self.icon_service) 70 | self.assertEqual(self._test1.get_address(), response) 71 | 72 | call = CallBuilder().from_(self._test1.get_address()).to(self._score_address).method("get_balance").\ 73 | params({"address": self._test1.get_address()}).build() 74 | response = self.process_call(call, self.icon_service) 75 | self.assertEqual(hex(10**24), response) 76 | 77 | call = CallBuilder().from_(self._test1.get_address()).to(self._score_address).method("simple_json_dumps")\ 78 | .build() 79 | response = self.process_call(call, self.icon_service) 80 | expected = json.dumps({"simple": "value"}) 81 | self.assertEqual(expected, response) 82 | -------------------------------------------------------------------------------- /tests/test_tbears_service.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright 2017-2018 ICON Foundation 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | import os 16 | import shutil 17 | import time 18 | import unittest 19 | 20 | from iconcommons.icon_config import IconConfig 21 | from iconsdk.builder.transaction_builder import TransactionBuilder 22 | from iconsdk.icon_service import IconService 23 | from iconsdk.providers.http_provider import HTTPProvider 24 | from iconsdk.signed_transaction import SignedTransaction 25 | from iconsdk.utils.convert_type import convert_hex_str_to_int 26 | from iconsdk.wallet.wallet import KeyWallet 27 | 28 | from tbears.block_manager.message_code import Response, responseCodeMap 29 | from tbears.command.command import Command 30 | from tbears.config.tbears_config import FN_CLI_CONF, tbears_server_config, FN_SERVER_CONF 31 | from tbears.util.arg_parser import uri_parser 32 | from tbears.util.transaction_logger import send_transaction_with_logger 33 | from tests.test_util import TEST_UTIL_DIRECTORY 34 | 35 | 36 | class TestTBearsService(unittest.TestCase): 37 | def setUp(self): 38 | self.cmd = Command() 39 | self.project_name = 'a_test' 40 | self.project_class = 'ATest' 41 | self.start_conf = None 42 | try: 43 | self.cmd.cmdServer.stop(None) 44 | self.cmd.cmdScore.clear(None) 45 | except: 46 | pass 47 | 48 | def tearDown(self): 49 | try: 50 | if os.path.exists(FN_CLI_CONF): 51 | os.remove(FN_CLI_CONF) 52 | if os.path.exists(FN_SERVER_CONF): 53 | os.remove(FN_SERVER_CONF) 54 | if os.path.exists('./tbears.log'): 55 | os.remove('./tbears.log') 56 | if os.path.exists(self.project_name): 57 | shutil.rmtree(self.project_name) 58 | if os.path.exists('exc'): 59 | shutil.rmtree('exc') 60 | self.cmd.cmdServer.stop(None) 61 | self.cmd.cmdScore.clear(None) 62 | except: 63 | pass 64 | 65 | def test_duplicated_tx(self): 66 | # test start, deploy, stop, clean command 67 | conf = self.cmd.cmdUtil.get_init_args(project=self.project_name, score_class=self.project_class) 68 | 69 | # init 70 | self.cmd.cmdUtil.init(conf) 71 | 72 | # start 73 | tbears_config_path = os.path.join(TEST_UTIL_DIRECTORY, f'test_tbears_server_config.json') 74 | start_conf = IconConfig(tbears_config_path, tbears_server_config) 75 | start_conf.load() 76 | start_conf['config'] = tbears_config_path 77 | self.start_conf = start_conf 78 | self.cmd.cmdServer.start(start_conf) 79 | self.assertTrue(self.cmd.cmdServer.is_service_running()) 80 | 81 | # prepare to send 82 | genesis_info = start_conf['genesis']['accounts'][0] 83 | from_addr = genesis_info['address'] 84 | 85 | uri = f'http://127.0.0.1:{start_conf["port"]}/api/v3' 86 | uri, version = uri_parser(uri) 87 | 88 | icon_service = IconService(HTTPProvider(uri, version)) 89 | 90 | to_addr = f'hx{"d" * 40}' 91 | timestamp = int(time.time() * 10 ** 6) 92 | 93 | transaction = TransactionBuilder()\ 94 | .from_(from_addr)\ 95 | .to(to_addr)\ 96 | .timestamp(timestamp)\ 97 | .step_limit(convert_hex_str_to_int('0x100000'))\ 98 | .build() 99 | 100 | wallet = KeyWallet.create() 101 | signed_transaction = SignedTransaction(transaction, wallet) 102 | signed_transaction.signed_transaction_dict['signature'] = 'sig' 103 | 104 | # send transaction 105 | response = send_transaction_with_logger(icon_service, signed_transaction, uri) 106 | self.assertTrue('result' in response) 107 | 108 | # send again 109 | response = send_transaction_with_logger(icon_service, signed_transaction, uri) 110 | self.assertTrue('error' in response) 111 | self.assertEqual(responseCodeMap[Response.fail_tx_invalid_duplicated_hash][1], response['error']['message']) 112 | -------------------------------------------------------------------------------- /tests/test_parsing_util.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 ICON Foundation 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import json 16 | import os 17 | import shutil 18 | 19 | from tbears.command.command_util import CommandUtil 20 | from tbears.config.tbears_config import FN_CLI_CONF, FN_SERVER_CONF 21 | from tbears.tbears_exception import TBearsCommandException 22 | 23 | from tests.test_parsing_command import TestCommand 24 | 25 | 26 | class TestCommandUtil(TestCommand): 27 | def setUp(self): 28 | super().setUp() 29 | self.tear_down_params = ['proj_unittest', 'proj_unittest_dir'] 30 | self.project = 'proj_unittest' 31 | self.score_class = 'TestClass' 32 | 33 | def tearDown(self): 34 | try: 35 | if os.path.exists(FN_CLI_CONF): 36 | os.remove(FN_CLI_CONF) 37 | if os.path.exists(FN_SERVER_CONF): 38 | os.remove(FN_SERVER_CONF) 39 | if os.path.exists('./tbears.log'): 40 | os.remove('./tbears.log') 41 | except: 42 | pass 43 | 44 | # Test if cli arguments are parsed correctly. 45 | def test_init_args_parsing(self): 46 | # Parsing test 47 | cmd = f'init {self.project} {self.score_class}' 48 | parsed = self.parser.parse_args(cmd.split()) 49 | self.assertEqual(parsed.command, 'init') 50 | self.assertEqual(parsed.project, self.project) 51 | self.assertEqual(parsed.score_class, self.score_class) 52 | 53 | # Insufficient argument 54 | cmd = f'init {self.project}' 55 | self.assertRaises(SystemExit, self.parser.parse_args, cmd.split()) 56 | 57 | def test_init_necessary_and_correct_args(self): 58 | project_dir = 'proj_unittest_dir' 59 | 60 | # Correct project name and class name 61 | cmd = f'init {self.project} {self.score_class}' 62 | parsed = self.parser.parse_args(cmd.split()) 63 | try: 64 | CommandUtil._check_init(vars(parsed)) 65 | except: 66 | exception_raised = True 67 | else: 68 | exception_raised = False 69 | self.assertFalse(exception_raised) 70 | 71 | # Project and score_class are same 72 | cmd = f'init {self.project} {self.project}' 73 | parsed = self.parser.parse_args(cmd.split()) 74 | self.assertRaises(TBearsCommandException, CommandUtil._check_init, vars(parsed)) 75 | 76 | # Input existing SCORE path when initializing the SCORE 77 | cmd = f'init {self.project} {self.score_class}' 78 | parsed = self.parser.parse_args(cmd.split()) 79 | self.touch(self.project) 80 | self.assertRaises(SystemExit, self.parser.parse_args, vars(parsed)) 81 | os.remove(self.project) 82 | 83 | # Input existing SCORE directory when initializing the SCORE. 84 | cmd = f'init {project_dir} {self.score_class}' 85 | parsed = self.parser.parse_args(cmd.split()) 86 | os.mkdir(project_dir) 87 | self.assertRaises(SystemExit, self.parser.parse_args, vars(parsed)) 88 | shutil.rmtree(project_dir) 89 | 90 | # check the value of "main_module" field 91 | cmd = f'init {self.project} {self.score_class}' 92 | parsed = self.parser.parse_args(cmd.split()) 93 | self.cmd.cmdUtil.init(conf=vars(parsed)) 94 | with open(f'{self.project}/package.json', mode='r') as package_contents: 95 | package_json = json.loads(package_contents.read()) 96 | main = package_json['main_module'] 97 | self.assertEqual(self.project, main) 98 | shutil.rmtree(self.project) 99 | 100 | def test_samples_args_parsing(self): 101 | # Parsing test 102 | cmd = f'samples' 103 | parsed = self.parser.parse_args(cmd.split()) 104 | self.assertEqual(parsed.command, 'samples') 105 | 106 | # Too many arguments 107 | cmd = f'samples arg1 arg2' 108 | self.assertRaises(SystemExit, self.parser.parse_args, cmd.split()) 109 | 110 | def test_genconf_args_parsing(self): 111 | # Parsing test 112 | cmd = f'genconf' 113 | parsed = self.parser.parse_args(cmd.split()) 114 | self.assertEqual(parsed.command, 'genconf') 115 | 116 | # Too many arguments 117 | cmd = f'genconf arg1 arg2' 118 | self.assertRaises(SystemExit, self.parser.parse_args, cmd.split()) 119 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/pycharm,python 3 | 4 | ### PyCharm ### 5 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 6 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 7 | 8 | # User-specific stuff: 9 | .idea/ 10 | .idea/workspace.xml 11 | .idea/tasks.xml 12 | .idea/dictionaries 13 | .idea/vcs.xml 14 | .idea/jsLibraryMappings.xml 15 | 16 | # Sensitive or high-churn files: 17 | .idea/dataSources.ids 18 | .idea/dataSources.xml 19 | .idea/dataSources.local.xml 20 | .idea/sqlDataSources.xml 21 | .idea/dynamic.xml 22 | .idea/uiDesigner.xml 23 | 24 | # Gradle: 25 | .idea/gradle.xml 26 | .idea/libraries 27 | 28 | # Mongo Explorer plugin: 29 | .idea/mongoSettings.xml 30 | 31 | ## File-based project format: 32 | *.iws 33 | 34 | ## Plugin-specific files: 35 | 36 | # IntelliJ 37 | /out/ 38 | 39 | # Sublime 40 | *.sublime-project 41 | *.sublime-workspace 42 | 43 | # Visual Studio Code 44 | .vscode/ 45 | 46 | # mpeltonen/sbt-idea plugin 47 | .idea_modules/ 48 | 49 | # JIRA plugin 50 | atlassian-ide-plugin.xml 51 | 52 | # Crashlytics plugin (for Android Studio and IntelliJ) 53 | com_crashlytics_export_strings.xml 54 | crashlytics.properties 55 | crashlytics-build.properties 56 | fabric.properties 57 | 58 | ### PyCharm Patch ### 59 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 60 | 61 | *.iml 62 | # modules.xml 63 | # .idea/misc.xml 64 | # *.ipr 65 | 66 | ### Grpc Samples and Personal note ### 67 | grpc 68 | worknote 69 | loopchain/peer/radiopeer_pb2.py 70 | loopchain/protos/loopchain_pb2*.py 71 | 72 | ### level db and sqlite dirs ### 73 | db 74 | db_* 75 | chaindb_* 76 | 77 | ### Spike TEST ### 78 | testcase/spike 79 | test.py 80 | 81 | ### Python ### 82 | # Byte-compiled / optimized / DLL files 83 | __pycache__/ 84 | *.py[cod] 85 | *$py.class 86 | 87 | # C extensions 88 | 89 | # Distribution / packaging 90 | .Python 91 | env/ 92 | build/ 93 | develop-eggs/ 94 | dist/ 95 | downloads/ 96 | eggs/ 97 | .eggs/ 98 | lib/ 99 | lib64/ 100 | parts/ 101 | sdist/ 102 | var/ 103 | *.egg-info/ 104 | .installed.cfg 105 | *.egg 106 | 107 | # PyInstaller 108 | # Usually these files are written by a python script from a template 109 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 110 | *.manifest 111 | *.spec 112 | 113 | # Installer logs 114 | pip-log.txt 115 | pip-delete-this-directory.txt 116 | pip-selfcheck.json 117 | 118 | # Unit test / coverage reports 119 | htmlcov/ 120 | .tox/ 121 | .coverage 122 | .coverage.* 123 | .cache 124 | nosetests.xml 125 | coverage.xml 126 | *,cover 127 | .hypothesis/ 128 | 129 | # Translations 130 | *.mo 131 | *.pot 132 | 133 | # Django stuff: 134 | *.log 135 | local_settings.py 136 | 137 | # Flask stuff: 138 | instance/ 139 | .webassets-cache 140 | 141 | # Scrapy stuff: 142 | .scrapy 143 | 144 | # Sphinx documentation 145 | docs/_build/ 146 | 147 | # PyBuilder 148 | target/ 149 | 150 | # IPython Notebook 151 | .ipynb_checkpoints 152 | 153 | # pyenv 154 | .python-version 155 | 156 | # celery beat schedule file 157 | celerybeat-schedule 158 | 159 | # dotenv 160 | .env 161 | 162 | # virtualenv 163 | .venv 164 | venv 165 | ENV/ 166 | bin/ 167 | lib/ 168 | include/ 169 | share/ 170 | 171 | # Spyder project settings 172 | .spyderproject 173 | 174 | # Rope project settings 175 | .ropeproject 176 | 177 | 178 | # ETC 179 | tmp/ 180 | testcase/db/ 181 | test_chain_helper_block/ 182 | sample_score 183 | servertool.py 184 | *_pb2.py 185 | *_profile.txt 186 | *_profile_.txt 187 | test_performance_result.txt 188 | # *.pem 189 | nouse_* 190 | resources/unittest/* 191 | docs_mine/ 192 | resources/test_score_deploy/ 193 | resources/test_score_repository/loopchain/ 194 | resources/test_score_repository/develop/test_score/deploy/ 195 | resources/test_score_repository/develop/test_query_exception/deploy/ 196 | resources/ssl_grpc_test_cert/ssl.crt 197 | resources/default_pki/*.der 198 | score/loopchain/ 199 | score/develop/dev_score/deploy 200 | score/develop/black_score/deploy 201 | score/develop/test_score/deploy 202 | score/develop/test_score/.git 203 | score/develop/certificate 204 | loopchain/configure_user.py 205 | loopchain/configure_user_ori.py 206 | testcase/unittest/blockchain_db* 207 | channel_manage_data_test2.json 208 | channel_manage_data_ori.json 209 | run_a_test.sh 210 | todo.md 211 | .*.swp 212 | log.txt 213 | 214 | gen_docs/_build 215 | gen_docs/docs 216 | 217 | cscope.* 218 | 219 | #!score/sample 220 | #score/ 221 | score/.database_store/ 222 | .storage 223 | 224 | deploy/LOOPCHAIN_VERSION 225 | parser_py.py 226 | 227 | resources/test_score_repository/develop/leveldb_test/deploy/ 228 | resources/test_score_repository/develop/score_duplicate_test/deploy/ 229 | score/score/ 230 | build/ 231 | dist/ 232 | 233 | #tbears 234 | .statedb 235 | .score 236 | tbears_cli_config.json 237 | 238 | # patch 239 | *.rej 240 | *.orig 241 | -------------------------------------------------------------------------------- /tests/simpleScore2/simpleScore2.py: -------------------------------------------------------------------------------- 1 | from iconservice import * 2 | 3 | 4 | class SimpleScoreInterface(InterfaceScore): 5 | @interface 6 | def setValue(self, value): pass 7 | 8 | @interface 9 | def getValue(self) -> str: pass 10 | 11 | 12 | class SimpleScore2(IconScoreBase): 13 | 14 | def __init__(self, db: IconScoreDatabase) -> None: 15 | super().__init__(db) 16 | self.value = VarDB("value1", db, value_type=str) 17 | self.array = ArrayDB("arrayDB", db, value_type=int) 18 | self.dict = DictDB("dictDB", db, value_type=int) 19 | self.score_address = VarDB("score_address", db, value_type=Address) 20 | 21 | @eventlog(indexed=0) 22 | def SetValue(self, value: str): pass 23 | 24 | @eventlog(indexed=1) 25 | def SetSCOREValue(self, value: str): pass 26 | 27 | def on_install(self, score_address: 'Address') -> None: 28 | super().on_install() 29 | self.score_address.set(score_address) 30 | 31 | def on_update(self, value: str) -> None: 32 | super().on_update() 33 | self.value.set(value) 34 | 35 | @external(readonly=True) 36 | def getValue(self) -> str: 37 | return self.value.get() 38 | 39 | @external 40 | def setValue(self, value: str): 41 | self._setValue(value) 42 | 43 | self.SetValue(value) 44 | 45 | @external 46 | def setSCOREValue(self, value: str): 47 | score = self.create_interface_score(self.score_address.get(), SimpleScoreInterface) 48 | score.setValue(value) 49 | 50 | self.SetSCOREValue(value) 51 | 52 | @external(readonly=True) 53 | def getSCOREValue(self) -> str: 54 | score = self.create_interface_score(self.score_address.get(), SimpleScoreInterface) 55 | 56 | return score.getValue() 57 | 58 | @external(readonly=True) 59 | def getSCOREValue2(self) -> str: 60 | ret = self.call(self.score_address.get(), "getValue", {}) 61 | return ret 62 | 63 | @external 64 | def appendValue(self, value: int): 65 | self.array.put(value) 66 | 67 | @external(readonly=True) 68 | def arrayLength(self) -> int: 69 | return len(self.array) 70 | 71 | @external 72 | def arraySetItem(self, index: int, value: int): 73 | self.array[index] = value 74 | 75 | @external(readonly=True) 76 | def arrayGetItem(self, index: int) -> int: 77 | return self.array[index] 78 | 79 | @external(readonly=True) 80 | def arraySum(self) -> int: 81 | # written for testing __iter__ 82 | return sum(self.array) 83 | 84 | @external 85 | def dictSetItem(self, key: int, value: int): 86 | self.dict[key] = value 87 | 88 | @external(readonly=True) 89 | def dictGetItem(self, key: int) -> int: 90 | return self.dict[key] 91 | 92 | @external(readonly=True) 93 | def dictContains(self, key: int) -> int: 94 | return key in self.dict 95 | 96 | @external(readonly=True) 97 | def write_on_readonly(self) -> str: 98 | self._setValue('3') 99 | return 'd' 100 | 101 | # This method is for understanding the ScoreTestCase.set_msg method. 102 | def t_msg(self): 103 | assert self.msg.sender == Address.from_string(f"hx{'1234' * 10}") 104 | assert self.msg.value == 3 105 | 106 | # This method is for understanding the ScoreTestCase.set_tx method. 107 | def t_tx(self): 108 | assert self.tx.origin == Address.from_string(f"hx{'1234' * 10}") 109 | 110 | # This method is for understanding the ScoreTestCase.set_block method. 111 | def t_block(self): 112 | assert self.block.height == 3 113 | assert self.block.timestamp == 30 114 | assert self.block_height == 3 115 | assert self.now() == 30 116 | 117 | @external(readonly=True) 118 | def get_balance(self, address: Address) -> int: 119 | return self.icx.get_balance(address) 120 | 121 | @external 122 | def transfer(self, to: Address, amount: int): 123 | self.icx.transfer(to, amount) 124 | 125 | @external 126 | def send(self, to: Address): 127 | self.icx.send(to, self.msg.value) 128 | 129 | @external 130 | def get_owner(self) -> Address: 131 | return self.owner 132 | 133 | @external 134 | def simple_json_dumps(self) -> str: 135 | return json_dumps({"simple": "value"}) 136 | 137 | def _setValue(self, value: str): 138 | self.value.set(value) 139 | 140 | def general_method(self) -> str: 141 | value = self.getValue() 142 | self.setValue(value * 2) 143 | return value 144 | 145 | @payable 146 | def donate(self): 147 | pass 148 | 149 | @payable 150 | def call_donate_payable(self): 151 | self.donate() 152 | 153 | def call_donate(self): 154 | self.donate() 155 | 156 | @external 157 | def call_donate_external(self): 158 | self.donate() 159 | 160 | @external(readonly=True) 161 | def call_donate_readonly(self): 162 | self.donate() 163 | -------------------------------------------------------------------------------- /tbears/profile_tbears/startup/00_first.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright 2018 ICON Foundation 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | import itertools 16 | 17 | from IPython import get_ipython 18 | from IPython.core.magic import magics_class, Magics, line_magic 19 | from IPython.core import page 20 | from IPython.terminal.prompts import Prompts, Token 21 | 22 | from tbears.command.command import Command 23 | 24 | ip = get_ipython() 25 | 26 | 27 | @magics_class 28 | class TbearsCommands(Magics): 29 | command_ins = Command() 30 | score_info = [] 31 | deployed_id = itertools.count(start=1) 32 | 33 | def run_command(self, command): 34 | try: 35 | full_command_list = f'{command}'.split() 36 | if full_command_list[0] == 'console': 37 | return 38 | response = self.command_ins.run(full_command_list) 39 | except: 40 | return 41 | else: 42 | global _r 43 | _r = response 44 | if isinstance(response, int): 45 | return 46 | 47 | if full_command_list[0] == 'deploy' and not response.get("error", None): 48 | args = self.command_ins.parser.parse_args(full_command_list) 49 | conf = self.command_ins.cmdScore.get_icon_conf('deploy', args=vars(args)) 50 | self.score_info.append(f"{next(self.deployed_id)}." 51 | f"path : {conf['project']}, txhash : {response['result']}," 52 | f" deployed in : {conf['uri']}") 53 | return 54 | 55 | @line_magic 56 | def tbears(self, line): 57 | return self.run_command(line) 58 | 59 | @line_magic 60 | def deployresults(self, line): 61 | return page.page("\n".join(self.score_info)) 62 | 63 | @line_magic 64 | def init(self, line): 65 | return self.run_command(f"init {line}") 66 | 67 | @line_magic 68 | def samples(self, line): 69 | return self.run_command(f"samples {line}") 70 | 71 | @line_magic 72 | def clear(self, line): 73 | return self.run_command(f"clear {line}") 74 | 75 | @line_magic 76 | def deploy(self, line): 77 | return self.run_command(f"deploy {line}") 78 | 79 | @line_magic 80 | def start(self, line): 81 | return self.run_command(f"start {line}") 82 | 83 | @line_magic 84 | def stop(self, line): 85 | return self.run_command(f"stop {line}") 86 | 87 | @line_magic 88 | def keystore(self, line): 89 | return self.run_command(f"keystore {line}") 90 | 91 | @line_magic 92 | def transfer(self, line): 93 | return self.run_command(f"transfer {line}") 94 | 95 | @line_magic 96 | def txresult(self, line): 97 | return self.run_command(f"txresult {line}") 98 | 99 | @line_magic 100 | def balance(self, line): 101 | return self.run_command(f"balance {line}") 102 | 103 | @line_magic 104 | def totalsupply(self, line): 105 | return self.run_command(f"totalsupply {line}") 106 | 107 | @line_magic 108 | def scoreapi(self, line): 109 | return self.run_command(f"scoreapi {line}") 110 | 111 | @line_magic 112 | def txbyhash(self, line): 113 | return self.run_command(f"txbyhash {line}") 114 | 115 | @line_magic 116 | def lastblock(self, line): 117 | return self.run_command(f"lastblock {line}") 118 | 119 | @line_magic 120 | def block(self, line): 121 | return self.run_command(f"block {line}") 122 | 123 | @line_magic 124 | def blockbyhash(self, line): 125 | return self.run_command(f"blockbyhash {line}") 126 | 127 | @line_magic 128 | def blockbyheight(self, line): 129 | return self.run_command(f"blockbyheight {line}") 130 | 131 | @line_magic 132 | def genconf(self, line): 133 | return self.run_command(f"genconf {line}") 134 | 135 | @line_magic 136 | def sendtx(self, line): 137 | return self.run_command(f"sendtx {line}") 138 | 139 | @line_magic 140 | def call(self, line): 141 | return self.run_command(f"call {line}") 142 | 143 | @line_magic 144 | def test(self, line): 145 | return self.run_command(f"test {line}") 146 | 147 | 148 | class MyPrompt(Prompts): 149 | def in_prompt_tokens(self, cli=None): 150 | return [(Token.Prompt, 'tbears) ')] 151 | 152 | def out_prompt_tokens(self): 153 | return [(Token.OutPrompt, '')] 154 | 155 | 156 | ip.register_magics(TbearsCommands) 157 | ip.prompts = MyPrompt(ip) 158 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # ChangeLog - T-Bears 2 | 3 | ## 1.8.0 - 2021-02-25 4 | * Update `sync_mainnet` command 5 | * add revision 12 6 | 7 | ## 1.7.2 - 2020-10-22 8 | * Update `sync_mainnet` command 9 | * add revision 10, 11 10 | * Fix SCORE unittest Framework (#81) 11 | 12 | ## 1.7.1 - 2020-08-10 13 | * Fix `sync_mainnet` command 14 | * Fix invalid governance SCORE 1.1.1 15 | 16 | ## 1.7.0 - 2020-07-07 17 | * Add `sync_mainnet` command 18 | * sync revision and governance SCORE with the mainnet 19 | * makes 4 main P-Reps to make decentralized network 20 | * Substitute `coincurve` for `secp256k1` 21 | * `genconf` command makes keystore files for 4 main P-Reps 22 | 23 | ## 1.6.4 - 2020-06-05 24 | * Fix deploy command (#70) 25 | 26 | ## 1.6.3 - 2020-06-02 27 | * Fix SCORE unittest framework (#69) 28 | 29 | ## 1.6.2 - 2020-05-29 30 | * Fix SCORE unittest framework (#68) 31 | * owner property bug 32 | * patch deprecated SCORE methods to warning 33 | * Fix minor bugs 34 | * error code for get_tx_info message with invalid TX hash 35 | * prevVotes list management 36 | * write_precommit_state message parameter 37 | 38 | ## 1.6.1 - 2020-05-20 39 | * Add --nid option to 'sendtx' command (#66) 40 | 41 | ## 1.6.0 - 2020-04-08 42 | * Support block version 0.3 and above (#59) 43 | * Apply iconsdk (#54) 44 | * Add 'keyinfo' command (#56) 45 | * Fix 'sendtx' command bug (#61) 46 | 47 | ## 1.5.0 - 2019-09-02 48 | * Support IISS 49 | 50 | ## 1.4.0 - 2019-08-19 51 | * Add 'keyinfo' command to query keyinfo file content 52 | * Fix the bug of 'transfer' command transfering invalid amount of ICX 53 | 54 | ## 1.3.0 - 2019-07-16 55 | * Update Docker image files for Fee 2.0 56 | 57 | ## 1.2.2 - 2019-06-21 58 | * Add the transaction signature verifier 59 | * Fix the bug of generating invalid signature after estimating Step 60 | 61 | ## 1.2.1 - 2019-06-12 62 | * Add `stepLimit` estimate logic to `deploy`, `sendtx`, and `transfer` commands 63 | * Apply block hash compatibility to test module 64 | 65 | ## 1.2.0 - 2019-05-22 66 | * Improve Docker image generation 67 | * Refactor sub processes management 68 | * Migrate `package.json` format 69 | * Add SCORE unittest framework 70 | 71 | ## 1.1.0.1 - 2019-01-03 72 | * Fix wrong description of blockConfirmInterval in README.md 73 | * Fix block query error 74 | 75 | ## 1.1.0 - 2018-11-29 76 | * Add test command 77 | * Add SCORE integration test library 78 | * Add -v, --verbose option 79 | * Modify init command to generate test code 80 | * Deprecate samples command 81 | 82 | ## 1.0.6.2 - 2018-10-19 83 | * Fix configuration file loading bug 84 | * Modify python version requirements to 3.6.5+ 85 | 86 | ## 1.0.6.1 - 2018-10-16 87 | * Fix deploy command bug 88 | 89 | ## 1.0.6 - 2018-10-12 90 | * Add '--step-limit' option to the 'deploy' and 'transfer' commands 91 | * Support docker image - iconloop/tbears 92 | * Fix minor bugs 93 | 94 | ## 1.0.5.1 - 2018-09-07 95 | * Remove sanic package from dependencies 96 | 97 | ## 1.0.5 - 2018-09-06 98 | * '-t' option of the deploy command deprecated. Deploy command supports zip type only. 99 | * Remove runtime warning message of T-Bears block_manager. 100 | * Modify the deploy command. Deploy command can get directory and zip file as a project. 101 | * Fix the transfer command to read 'stepLimit' from the configuration file. 102 | * Add test account 'test1' in genesis block. 103 | * Modify the keystore command to receive the password twice to confirm it. 104 | 105 | ## 1.0.4 - 2018-08-28 106 | * Change the encryption parameter 'N' of keystore 107 | * Improve IconClient error handling 108 | * Update README.md 109 | 110 | ## 1.0.3 - 2018-08-24 111 | * Add interactive mode. Activate with 'console' command 112 | * Add '-p' option to 'deploy' command. Now can enter keystore file password as argument. 113 | * Add block manager. 114 | * Rename tbears_tutorial.md to README.md 115 | 116 | ## 1.0.0 - 2018-08-15 117 | * Tbears service support 'call', 'sendtx' and 'genconf' command 118 | * call : Send icx_call request 119 | * sendtx : Send icx_sendTransaction request 120 | * genconf : Generate tbears configuration files. (tbears_server_config.json, tbears_cli_config.json) 121 | 122 | ## 0.9.8 - 2018-08-09 123 | * Tbears service support 'lastblock', 'blockbyhash' and 'blockbyheight' command 124 | * Can query block information on tbears service now 125 | * Proofread tbears_tutorial.md 126 | 127 | ## 0.9.7 - 2018-08-03 128 | * Fix the bug that '-a' option of start command does not work 129 | * Exception logs are saved separately 130 | 131 | ## 0.9.6.1 - 2018-07-31 132 | 133 | * Fix configuration file loading bug 134 | * Add commands (lastblock, blockbyhash, blockbyheight) 135 | * update tbears_tutorial.md 136 | 137 | ## 0.9.6 - 2018-07-27 138 | 139 | * Support log rotation with ICON Commons 140 | * Add commands (balance, totalsupply, scoreapi) 141 | * tbears_tutorial.md is out of date. It will be updated later 142 | 143 | ## 0.9.5.1 - 2018-07-24 144 | 145 | * Improve configuration files 146 | * check 'Configuration Files' chapter of tbears_tutorial.md 147 | 148 | ## 0.9.5 - 2018-07-20 149 | 150 | * Use ICON common package for configuration file and logging module 151 | * Improve the output message of deploy command 152 | * Success response : print transaction hash in response 153 | * Error response : print response message 154 | * Add command 155 | * txresult : Get transaction result by transaction hash 156 | * transfer: Transfer ICX coin 157 | * Add network ID 158 | * Check icx_sendTransaction method parameter 'nid' of tbears JSON-RPC API document 159 | * 'Add -n', '--nid' option on deploy command. 160 | -------------------------------------------------------------------------------- /tbears/libs/scoretest/patch/context.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright 2018 ICON Foundation 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | from typing import Optional, TYPE_CHECKING 17 | from unittest.mock import Mock 18 | 19 | from iconservice import IconScoreBase 20 | from iconservice.base.exception import InvalidParamsException 21 | from iconservice.base.message import Message 22 | from iconservice.base.transaction import Transaction 23 | from iconservice.icon_constant import IconScoreContextType, IconScoreFuncType 24 | from iconservice.iconscore.icon_score_context import IconScoreContext 25 | 26 | from ..mock.block import Block 27 | from ..mock.db import MockKeyValueDatabase 28 | from ..mock.icx_engine import IcxEngine 29 | from ....libs.icon_integrate_test import create_tx_hash 30 | 31 | if TYPE_CHECKING: 32 | from iconservice.base.address import Address 33 | 34 | score_mapper = {} 35 | interface_score_mapper = {} 36 | MAIN_NET_REVISION = 8 37 | context_db = MockKeyValueDatabase.create_db() # Patch SCORE to use dictionary DB instance of LEVEL DB 38 | IcxEngine.db = context_db 39 | icon_network_value = {} 40 | 41 | 42 | def get_icon_score(address: 'Address'): 43 | """This method will be called when SCORE call get_icon_score method while test using score-unittest-framework 44 | 45 | :param address: address of SCORE 46 | :return: SCORE 47 | """ 48 | if address.is_contract is False: 49 | raise InvalidParamsException(f"{address} is not SCORE") 50 | elif isinstance(score_mapper.get(address, None), IconScoreBase) is False: 51 | raise InvalidParamsException(f"{address} is not active SCORE") 52 | return score_mapper[address] 53 | 54 | 55 | def get_default_context(): 56 | mock_context = Mock(spec=IconScoreContext) 57 | mock_context.configure_mock(msg=Message()) 58 | mock_context.configure_mock(tx=Transaction()) 59 | mock_context.configure_mock(block=Block()) 60 | mock_context.configure_mock(step_counter=None) 61 | mock_context.configure_mock(type=IconScoreContextType.QUERY) 62 | mock_context.configure_mock(func_type=IconScoreFuncType.WRITABLE) 63 | mock_context.configure_mock(current_address=None) 64 | mock_context.configure_mock(block_batch=None) 65 | mock_context.configure_mock(tx_batch=None) 66 | mock_context.configure_mock(event_logs=None) 67 | mock_context.configure_mock(event_log_stack=[]) 68 | mock_context.configure_mock(traces=[]) 69 | mock_context.configure_mock(icon_score_mapper=score_mapper) 70 | mock_context.configure_mock(revision=MAIN_NET_REVISION) 71 | mock_context.icon_score_mapper = score_mapper 72 | mock_context.validate_score_blacklist = Mock(return_value=True) 73 | mock_context.get_icon_score = get_icon_score 74 | mock_context.method_flag_trace = [] 75 | return mock_context 76 | 77 | 78 | def clear_data(): 79 | IcxEngine.db.close() 80 | score_mapper.clear() 81 | interface_score_mapper.clear() 82 | 83 | 84 | class Context: 85 | context = get_default_context() 86 | 87 | @classmethod 88 | def get_context(cls): 89 | return cls.context 90 | 91 | @classmethod 92 | def reset_context(cls): 93 | cls.context = get_default_context() 94 | 95 | @staticmethod 96 | def _set_invoke_context(context): 97 | context.type = IconScoreContextType.INVOKE 98 | context.event_logs = [] 99 | context.func_type = IconScoreFuncType.WRITABLE 100 | context.readonly = False 101 | 102 | @staticmethod 103 | def _set_query_context(context): 104 | context.type = IconScoreContextType.QUERY 105 | context.func_type = IconScoreFuncType.READONLY 106 | context.readonly = True 107 | 108 | @classmethod 109 | def set_msg(cls, sender: Optional['Address'] = None, value: int = 0): 110 | context = cls.context 111 | cls._set_msg(context, sender, value) 112 | 113 | @classmethod 114 | def set_tx(cls, origin: Optional['Address'] = None, timestamp: Optional[int] = None, _hash: Optional[bytes] = None, 115 | index: int = 0, nonce: int = 0): 116 | context = cls.context 117 | cls._set_tx(context, origin, timestamp, _hash, index, nonce) 118 | 119 | @classmethod 120 | def set_block(cls, height: int = 0, timestamp: Optional[int] = None): 121 | context = cls.context 122 | cls._set_block(context, height, timestamp) 123 | 124 | @staticmethod 125 | def _set_msg(context, sender: Optional['Address'] = None, value: int = 0): 126 | msg = context.msg 127 | msg.sender = sender 128 | msg.value = value 129 | 130 | @staticmethod 131 | def _set_tx(context, origin: Optional['Address'] = None, timestamp: Optional[int] = None, 132 | _hash: Optional[bytes] = None, index: int = 0, nonce: int = 0): 133 | tx = context.tx 134 | tx._origin = origin 135 | tx._timestamp = timestamp 136 | tx._hash = _hash if _hash is not None else create_tx_hash() 137 | tx._index = index 138 | tx._nonce = nonce 139 | 140 | @staticmethod 141 | def _set_block(context, height: int = 0, timestamp: Optional[int] = None): 142 | block: Block = context.block 143 | block._height = height 144 | if timestamp: 145 | block.timestamp = timestamp 146 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Configuration file for the Sphinx documentation builder. 4 | # 5 | # This file does only contain a selection of the most common options. For a 6 | # full list see the documentation: 7 | # http://www.sphinx-doc.org/en/master/config 8 | 9 | # -- Path setup -------------------------------------------------------------- 10 | 11 | # If extensions (or modules to document with autodoc) are in another directory, 12 | # add these directories to sys.path here. If the directory is relative to the 13 | # documentation root, use os.path.abspath to make it absolute, like shown here. 14 | # 15 | import os 16 | import sys 17 | 18 | import sphinx_rtd_theme 19 | 20 | sys.path.insert(0, os.path.abspath('..')) 21 | 22 | 23 | # -- Project information ----------------------------------------------------- 24 | 25 | project = 'tbears' 26 | copyright = '2018, theloop' 27 | author = 'theloop' 28 | 29 | # The short X.Y version 30 | version = '' 31 | # The full version, including alpha/beta/rc tags 32 | release = '0.9.3' 33 | 34 | 35 | # -- General configuration --------------------------------------------------- 36 | 37 | # If your documentation needs a minimal Sphinx version, state it here. 38 | # 39 | # needs_sphinx = '1.0' 40 | 41 | # Add any Sphinx extension module names here, as strings. They can be 42 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 43 | # ones. 44 | extensions = [ 45 | 'sphinx.ext.autodoc', 46 | ] 47 | 48 | # Add any paths that contain templates here, relative to this directory. 49 | templates_path = ['_templates'] 50 | 51 | # The suffix(es) of source filenames. 52 | # You can specify multiple suffix as a list of string: 53 | # 54 | # source_suffix = ['.rst', '.md'] 55 | source_suffix = '.rst' 56 | 57 | # The master toctree document. 58 | master_doc = 'index' 59 | 60 | # The language for content autogenerated by Sphinx. Refer to documentation 61 | # for a list of supported languages. 62 | # 63 | # This is also used if you do content translation via gettext catalogs. 64 | # Usually you set "language" from the command line for these cases. 65 | language = None 66 | 67 | # List of patterns, relative to source directory, that match files and 68 | # directories to ignore when looking for source files. 69 | # This pattern also affects html_static_path and html_extra_path . 70 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 71 | 72 | # The name of the Pygments (syntax highlighting) style to use. 73 | pygments_style = 'sphinx' 74 | 75 | 76 | # -- Options for HTML output ------------------------------------------------- 77 | 78 | # The theme to use for HTML and HTML Help pages. See the documentation for 79 | # a list of builtin themes. 80 | # 81 | html_theme = 'sphinx_rtd_theme' 82 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] 83 | # Theme options are theme-specific and customize the look and feel of a theme 84 | # further. For a list of options available for each theme, see the 85 | # documentation. 86 | 87 | html_theme_options = { 88 | 'canonical_url': '', 89 | 'analytics_id': '', 90 | 'logo_only': False, 91 | 'display_version': True, 92 | 'prev_next_buttons_location': 'bottom', 93 | 'style_external_links': False, 94 | 'vcs_pageview_mode': '', 95 | # Toc options 96 | 'collapse_navigation': True, 97 | 'sticky_navigation': True, 98 | 'navigation_depth': 4, 99 | 'includehidden': True, 100 | 'titles_only': False 101 | } 102 | 103 | # Add any paths that contain custom static files (such as style sheets) here, 104 | # relative to this directory. They are copied after the builtin static files, 105 | # so a file named "default.css" will overwrite the builtin "default.css". 106 | html_static_path = ['_static'] 107 | 108 | # Custom sidebar templates, must be a dictionary that maps document names 109 | # to template names. 110 | # 111 | # The default sidebars (for documents that don't match any pattern) are 112 | # defined by theme itself. Builtin themes are using these templates by 113 | # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', 114 | # 'searchbox.html']``. 115 | # 116 | # html_sidebars = {} 117 | 118 | 119 | # -- Options for HTMLHelp output --------------------------------------------- 120 | 121 | # Output file base name for HTML help builder. 122 | htmlhelp_basename = 'tbearsdoc' 123 | 124 | 125 | # -- Options for LaTeX output ------------------------------------------------ 126 | 127 | latex_elements = { 128 | # The paper size ('letterpaper' or 'a4paper'). 129 | # 130 | # 'papersize': 'letterpaper', 131 | 132 | # The font size ('10pt', '11pt' or '12pt'). 133 | # 134 | # 'pointsize': '10pt', 135 | 136 | # Additional stuff for the LaTeX preamble. 137 | # 138 | # 'preamble': '', 139 | 140 | # Latex figure (float) alignment 141 | # 142 | # 'figure_align': 'htbp', 143 | } 144 | 145 | # Grouping the document tree into LaTeX files. List of tuples 146 | # (source start file, target name, title, 147 | # author, documentclass [howto, manual, or own class]). 148 | latex_documents = [ 149 | (master_doc, 'tbears.tex', 'tbears Documentation', 150 | 'theloop', 'manual'), 151 | ] 152 | 153 | 154 | # -- Options for manual page output ------------------------------------------ 155 | 156 | # One entry per manual page. List of tuples 157 | # (source start file, name, description, authors, manual section). 158 | man_pages = [ 159 | (master_doc, 'tbears', 'tbears Documentation', 160 | [author], 1) 161 | ] 162 | 163 | 164 | # -- Options for Texinfo output ---------------------------------------------- 165 | 166 | # Grouping the document tree into Texinfo files. List of tuples 167 | # (source start file, target name, title, author, 168 | # dir menu entry, description, category) 169 | texinfo_documents = [ 170 | (master_doc, 'tbears', 'tbears Documentation', 171 | author, 'tbears', 'One line description of project.', 172 | 'Miscellaneous'), 173 | ] 174 | 175 | 176 | # -- Extension configuration ------------------------------------------------- -------------------------------------------------------------------------------- /tests/test_argparse_type.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright 2017-2018 ICON Foundation 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | import shutil 17 | import unittest 18 | 19 | from tbears.util.argparse_type import * 20 | 21 | 22 | class TestArgParseType(unittest.TestCase): 23 | @staticmethod 24 | def touch(path): 25 | with open(path, 'a'): 26 | os.utime(path, None) 27 | 28 | def test_icon_path(self): 29 | file_path = './icon_path_test' 30 | 31 | # 'r' mode 32 | icon_path = IconPath() 33 | self.assertRaises(ArgumentTypeError, icon_path, file_path) 34 | 35 | self.touch(file_path) 36 | self.assertEqual(icon_path(file_path), file_path) 37 | 38 | # 'w' mode 39 | icon_path = IconPath('w') 40 | self.assertRaises(ArgumentTypeError, icon_path, file_path) 41 | 42 | os.remove(file_path) 43 | self.assertEqual(icon_path(file_path), file_path) 44 | 45 | # 'd' mode 46 | icon_path = IconPath('d') 47 | dir_path = 'dir_not_exist' 48 | self.assertRaises(ArgumentTypeError, icon_path, dir_path) 49 | 50 | dir_path = 'dir_path_test_file' 51 | self.touch(dir_path) 52 | self.assertRaises(ArgumentTypeError, icon_path, dir_path) 53 | os.remove(dir_path) 54 | 55 | dir_path = 'dir_path_test' 56 | os.mkdir(dir_path) 57 | self.assertEqual(icon_path(dir_path), dir_path) 58 | shutil.rmtree(dir_path) 59 | 60 | def test_icon_address(self): 61 | icon_addr = IconAddress('all') 62 | 63 | # valid address 64 | addr = f'cx{"a"*40}' 65 | self.assertEqual(icon_addr(addr), addr) 66 | addr = f'hx{"b"*40}' 67 | self.assertEqual(icon_addr(addr), addr) 68 | addr = f'hx{"0"*40}' 69 | self.assertEqual(icon_addr(addr), addr) 70 | 71 | # length < 42 72 | self.assertRaises(ArgumentTypeError, icon_addr, f'cx1') 73 | 74 | # length > 42 75 | self.assertRaises(ArgumentTypeError, icon_addr, f'cx{"0"*40}1') 76 | 77 | # upper case 78 | self.assertRaises(ArgumentTypeError, icon_addr, f'cx{"0"*39}A') 79 | 80 | # None hex 81 | self.assertRaises(ArgumentTypeError, icon_addr, f'cx{"0"*39}k') 82 | 83 | # hx prefix 84 | icon_addr = IconAddress('hx') 85 | 86 | addr = f'hx{"b"*40}' 87 | self.assertEqual(icon_addr(addr), addr) 88 | 89 | self.assertRaises(ArgumentTypeError, icon_addr, f'cx{"b"*40}') 90 | 91 | # cx prefix 92 | icon_addr = IconAddress('cx') 93 | 94 | addr = f'cx{"b"*40}' 95 | self.assertEqual(icon_addr(addr), addr) 96 | 97 | self.assertRaises(ArgumentTypeError, icon_addr, f'hx{"b"*40}') 98 | 99 | def test_hash_type(self): 100 | # valid hash 101 | hash_str = f'0x{"b"*64}' 102 | self.assertEqual(hash_type(hash_str), hash_str) 103 | 104 | # invalid type 105 | hash_str = 0x1234567890123456789012345678901234567890123456789012345678901234 106 | self.assertRaises(ArgumentTypeError, hash_type, hash_str) 107 | 108 | # invalid prefix 109 | hash_str = f'hx{"b"*64}' 110 | self.assertRaises(ArgumentTypeError, hash_type, hash_str) 111 | 112 | # length < 66 113 | self.assertRaises(ArgumentTypeError, hash_type, '0x1') 114 | 115 | # length > 66 116 | hash_str = f'0x{"b"*64}1' 117 | self.assertRaises(ArgumentTypeError, hash_type, hash_str) 118 | 119 | # upper case 120 | hash_str = f'0x{"b"*63}B' 121 | self.assertRaises(ArgumentTypeError, hash_type, hash_str) 122 | 123 | # None hex 124 | hash_str = f'0x{"b"*63}k' 125 | self.assertRaises(ArgumentTypeError, hash_type, hash_str) 126 | 127 | def test_port_type(self): 128 | # valid port 129 | port_str = "9000" 130 | self.assertEqual(port_type(port_str), int(port_str, 10)) 131 | 132 | # invalid type 133 | port_str = 9000 134 | self.assertRaises(ArgumentTypeError, port_type, port_str) 135 | 136 | # invalid hex string 137 | port_str = '0x1' 138 | self.assertRaises(ArgumentTypeError, port_type, port_str) 139 | 140 | # value < 0 141 | port_str = '-1' 142 | self.assertRaises(ArgumentTypeError, port_type, port_str) 143 | 144 | # value > 65535 145 | port_str = '65536' 146 | self.assertRaises(ArgumentTypeError, port_type, port_str) 147 | 148 | def test_non_negative_num_type(self): 149 | # valid int 150 | int_str = "1" 151 | self.assertEqual(non_negative_num_type(int_str), hex(int(int_str, 10))) 152 | int_str = "1a" 153 | self.assertEqual(non_negative_num_type(int_str), hex(int(int_str, 16))) 154 | int_str = "0x1a" 155 | self.assertEqual(non_negative_num_type(int_str), int_str) 156 | int_str = "0" 157 | self.assertEqual(non_negative_num_type(int_str), hex(int(int_str, 10))) 158 | int_str = "0x0" 159 | self.assertEqual(non_negative_num_type(int_str), int_str) 160 | 161 | # invalid type 162 | int_str = 9000 163 | self.assertRaises(ArgumentTypeError, non_negative_num_type, int_str) 164 | 165 | # invalid hex string 166 | int_str = '1k' 167 | self.assertRaises(ArgumentTypeError, non_negative_num_type, int_str) 168 | int_str = '0x1k' 169 | self.assertRaises(ArgumentTypeError, non_negative_num_type, int_str) 170 | 171 | # invalid negative number 172 | int_str = "-1" 173 | self.assertRaises(ArgumentTypeError, non_negative_num_type, int_str) 174 | int_str = "-0x1" 175 | self.assertRaises(ArgumentTypeError, non_negative_num_type, int_str) 176 | 177 | def test_loop(self): 178 | # valid values 179 | int_str = "1" 180 | self.assertEqual(loop(int_str), 1) 181 | # small float value 182 | significand, exponent = 1, 18 183 | small_float_str = f"{significand}e{exponent}" 184 | self.assertEqual(loop(small_float_str), int(float(small_float_str))) 185 | # big float value 186 | significand, exponent = 30, 30 187 | big_float_str = f"{significand}e{exponent}" 188 | self.assertEqual(loop(big_float_str), significand*10**exponent) 189 | 190 | # invalid values 191 | # alphabets 192 | alphabet = "xyz" 193 | self.assertRaises(ArgumentTypeError, loop, alphabet) 194 | # hex string 195 | hex_string = "0x12" 196 | self.assertRaises(ArgumentTypeError, loop, hex_string) 197 | -------------------------------------------------------------------------------- /tbears/block_manager/message_code.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 ICON Foundation 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """ A message class for the loopchain """ 16 | 17 | class Request: 18 | status = 1 19 | is_alive = 2 20 | stop = -9 21 | 22 | peer_peer_list = 600 23 | peer_get_leader = 601 # get leader peer object 24 | peer_complain_leader = 602 # complain leader peer is no response 25 | peer_reconnect_to_rs = 603 # reconnect to rs when rs restart detected. 26 | peer_restart_channel = 604 27 | 28 | rs_get_configuration = 800 29 | rs_set_configuration = 801 30 | rs_send_channel_manage_info_to_rs = 802 31 | rs_restart_channel = 803 32 | rs_delete_peer = 804 33 | 34 | tx_connect_to_leader = 901 # connect to leader 35 | tx_connect_to_inner_peer = 902 # connect to mother peer service in same inner gRPC micro service network 36 | get_tx_result = 903 # json-rpc:icx_getTransactionResult 37 | get_balance = 905 # josn-rpc:icx_getBalance 38 | get_tx_by_address = 906 # json-rpc:icx_getTransactionByAddress 39 | get_total_supply = 907 # json-rpc:icx_getTotalSupply 40 | 41 | 42 | class MetaParams: 43 | class ScoreLoad: 44 | repository_path = "repository_path" 45 | score_package = "score_package" 46 | base = "base" 47 | peer_id = "peer_id" 48 | 49 | class ScoreInfo: 50 | score_id = "score_id" 51 | score_version = "score_version" 52 | 53 | 54 | class Response: 55 | success = 0 56 | success_validate_block = 1 57 | success_announce_block = 2 58 | fail = -1 59 | fail_validate_block = -2 60 | fail_announce_block = -3 61 | fail_wrong_block_hash = -4 62 | fail_no_leader_peer = -5 63 | fail_validate_params = -6 64 | fail_made_block_count_limited = -7 65 | fail_wrong_subscribe_info = -8 66 | fail_connect_to_leader = -9 67 | fail_add_tx_to_leader = -10 68 | fail_create_tx = -11 69 | fail_invalid_peer_target = -12 70 | fail_not_enough_data = -13 71 | fail_tx_pre_validate = -14 72 | fail_subscribe_limit = -15 73 | fail_invalid_key_error = -16 74 | fail_wrong_block_height = -17 75 | fail_tx_invalid_unknown = -100 76 | fail_tx_invalid_hash_format = -101 77 | fail_tx_invalid_hash_generation = -102 78 | fail_tx_invalid_hash_not_match = -103 79 | fail_tx_invalid_address_not_match = -104 80 | fail_tx_invalid_address_format = -105 81 | fail_tx_invalid_signature = -106 82 | fail_tx_invalid_params = -107 83 | fail_tx_invalid_duplicated_hash = -108 84 | fail_tx_invalid_out_of_time_bound = -109 85 | fail_tx_invalid_wrong_nid = -110 86 | fail_tx_not_invoked = -111 87 | 88 | fail_no_peer_info_in_rs = -800 89 | timeout_exceed = -900 90 | not_treat_message_code = -999 91 | fail_illegal_params = -1000 92 | 93 | 94 | responseCodeMap = { 95 | Response.success: 96 | (Response.success,"success"), 97 | 98 | Response.success_validate_block: 99 | (Response.success_validate_block, "success validate block"), 100 | 101 | Response.success_announce_block: 102 | (Response.success_announce_block, "success announce block"), 103 | 104 | Response.fail: 105 | (Response.fail, "fail"), 106 | 107 | Response.fail_validate_block: 108 | (Response.fail_validate_block, "fail validate block"), 109 | 110 | Response.fail_announce_block: 111 | (Response.fail_announce_block, "fail announce block"), 112 | 113 | Response.fail_wrong_block_hash: 114 | (Response.fail_wrong_block_hash, "fail wrong block hash"), 115 | 116 | Response.fail_no_leader_peer: 117 | (Response.fail_no_leader_peer, "fail no leader peer"), 118 | 119 | Response.fail_validate_params: 120 | (Response.fail_validate_params, "fail validate params"), 121 | 122 | Response.fail_wrong_subscribe_info: 123 | (Response.fail_wrong_subscribe_info, "fail wrong subscribe info"), 124 | 125 | Response.fail_connect_to_leader: 126 | (Response.fail_connect_to_leader, "fail connect to leader"), 127 | 128 | Response.fail_add_tx_to_leader: 129 | (Response.fail_add_tx_to_leader, "fail add tx to leader"), 130 | 131 | Response.fail_invalid_peer_target: 132 | (Response.fail_invalid_peer_target, "fail invalid peer target for channel"), 133 | 134 | Response.fail_not_enough_data: 135 | (Response.fail_not_enough_data, "fail not enough data"), 136 | 137 | Response.fail_tx_pre_validate: 138 | (Response.fail_tx_pre_validate, "fail tx pre-validate"), 139 | 140 | Response.fail_subscribe_limit: 141 | (Response.fail_subscribe_limit, "fail subscribe limit"), 142 | 143 | Response.fail_no_peer_info_in_rs: 144 | (Response.fail_no_peer_info_in_rs, "fail no peer info in radio station"), 145 | 146 | Response.fail_create_tx: 147 | (Response.fail_create_tx, "fail create tx to peer"), 148 | 149 | Response.fail_wrong_block_height: 150 | (Response.fail_wrong_block_height, "fail wrong block height"), 151 | 152 | Response.fail_tx_invalid_unknown: 153 | (Response.fail_tx_invalid_unknown, "fail tx invalid unknown"), 154 | 155 | Response.fail_tx_invalid_hash_format: 156 | (Response.fail_tx_invalid_hash_format, "fail tx invalid hash format"), 157 | 158 | Response.fail_tx_invalid_hash_generation: 159 | (Response.fail_tx_invalid_hash_generation, "fail tx invalid hash generation"), 160 | 161 | Response.fail_tx_invalid_address_not_match: 162 | (Response.fail_tx_invalid_address_not_match, "fail tx invalid address not match"), 163 | 164 | Response.fail_tx_invalid_address_format: 165 | (Response.fail_tx_invalid_address_format, "fail tx invalid address"), 166 | 167 | Response.fail_tx_invalid_hash_not_match: 168 | (Response.fail_tx_invalid_hash_not_match, "fail tx invalid hash not match"), 169 | 170 | Response.fail_tx_invalid_signature: 171 | (Response.fail_tx_invalid_signature, "fail tx invalid signature"), 172 | 173 | Response.fail_tx_invalid_params: 174 | (Response.fail_tx_invalid_params, "fail tx invalid params"), 175 | 176 | Response.fail_tx_invalid_duplicated_hash: 177 | (Response.fail_tx_invalid_duplicated_hash, "fail tx invalid duplicated hash"), 178 | 179 | Response.fail_tx_invalid_out_of_time_bound: 180 | (Response.fail_tx_invalid_out_of_time_bound, "fail tx invalid out of time bound"), 181 | 182 | Response.fail_tx_invalid_wrong_nid: 183 | (Response.fail_tx_invalid_wrong_nid, "fail tx invalid no nid"), 184 | 185 | Response.fail_tx_not_invoked: 186 | (Response.fail_tx_not_invoked, "Pending transaction"), 187 | 188 | Response.timeout_exceed: 189 | (Response.timeout_exceed, "timeout exceed"), 190 | 191 | Response.fail_illegal_params: 192 | (Response.fail_illegal_params, "fail_illegal_params") 193 | } 194 | 195 | 196 | def get_response_code(code): 197 | return responseCodeMap[code][0] 198 | 199 | 200 | def get_response_msg(code): 201 | return responseCodeMap[code][1] 202 | 203 | 204 | def get_response(code): 205 | return responseCodeMap[code][0], responseCodeMap[code][1] 206 | -------------------------------------------------------------------------------- /tbears/block_manager/channel_service.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 ICON Foundation 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import base64 16 | import hashlib 17 | import json 18 | from typing import Tuple, TYPE_CHECKING, Optional 19 | 20 | from earlgrey import MessageQueueService, message_queue_task 21 | from iconcommons.logger import Logger 22 | 23 | from . import message_code 24 | from .tx_verifier import verify_signature 25 | from iconsdk.libs.serializer import serialize 26 | from ..util import create_hash 27 | 28 | if TYPE_CHECKING: 29 | from earlgrey import RobustConnection 30 | from tbears.block_manager.block_manager import BlockManager 31 | 32 | 33 | class ChannelInnerTask(object): 34 | """ 35 | Receive request from 'channel' message queue and send response 36 | """ 37 | 38 | def __init__(self, block_manager: 'BlockManager'): 39 | self._block_manager = block_manager 40 | self.confirmed_tx_list = list() 41 | 42 | @message_queue_task 43 | async def get_invoke_result(self, tx_hash: str) -> Tuple[int, Optional[str]]: 44 | """ 45 | Handler of 'get_invoke_result' message. 'get_invoke_result' is generated by 'icx_getTransactionResult' 46 | :param tx_hash: 47 | :return: message code and transaction result information 48 | """ 49 | Logger.debug(f'Get getTransactionResult tx_hash: {tx_hash}') 50 | block = self._block_manager._block 51 | 52 | tx_data_json = block.get_txresult(tx_hash=tx_hash) 53 | if tx_data_json is None: 54 | return message_code.Response.fail_tx_not_invoked, None 55 | 56 | return message_code.Response.success, tx_data_json 57 | 58 | @message_queue_task 59 | async def get_tx_info(self, tx_hash: str) -> Tuple[int, dict]: 60 | """ 61 | Handler of 'get_tx_info' message. 'get_tx_info' is generated by 'icx_getTransactionByHash' 62 | :param tx_hash: transaction hash 63 | :return: message code and transaction information 64 | """ 65 | Logger.debug(f'Get getTransactionByHash tx_hash: {tx_hash}') 66 | block = self._block_manager._block 67 | 68 | tx_data_json = block.get_transaction(tx_hash=tx_hash) 69 | if tx_data_json is None: 70 | return message_code.Response.fail_invalid_key_error, None 71 | 72 | return message_code.Response.success, tx_data_json 73 | 74 | @message_queue_task 75 | async def get_block(self, block_height: int, block_hash: str) \ 76 | -> Tuple[int, str, bytes, str]: 77 | """ 78 | Handler of 'get_block' message. 'get_block' is generated by 'icx_getLastBlock', 'icx_getBlockByHeight' and 79 | 'icx_getBlockByHash' 80 | :param block_height: block height 81 | :param block_hash: block hash 82 | :return: message code, block hash, confirm information and block data json string 83 | """ 84 | Logger.debug(f'Get get_block message block_height: {block_height}, block_hash: {block_hash}', "block") 85 | block = self._block_manager._block 86 | 87 | fail_response_code: int = None 88 | 89 | if block_hash == "" and block_height == -1: 90 | # getLastBlock 91 | block_data_json = block.get_last_block() 92 | if block_data_json is None: 93 | fail_response_code = message_code.Response.fail_wrong_block_hash 94 | elif block_hash: 95 | # getBlockByHash 96 | block_data_json = block.get_block_by_hash(block_hash=block_hash) 97 | if block_data_json is None: 98 | fail_response_code = message_code.Response.fail_wrong_block_hash 99 | else: 100 | # getBlockByHeight 101 | block_data_json = block.get_block_by_height(block_height=block_height) 102 | if block_data_json is None: 103 | fail_response_code = message_code.Response.fail_wrong_block_height 104 | 105 | if fail_response_code: 106 | return fail_response_code, block_hash, b'', "" 107 | 108 | block_hash: str = block_data_json.get('hash') 109 | # below block v0.3 110 | block_hash: str = block_hash if block_hash else block_data_json['block_hash'] 111 | block_data_json_str: str = json.dumps(block_data_json) 112 | 113 | # tbears does not support filters 114 | 115 | Logger.debug(f'Response block!!', "block") 116 | return message_code.Response.success, block_hash, b'0x1', block_data_json_str 117 | 118 | 119 | class ChannelService(MessageQueueService[ChannelInnerTask]): 120 | TaskType = ChannelInnerTask 121 | 122 | def _callback_connection_close(self, exc: Exception): 123 | Logger.error(f'[ChannelService] close message queue connection. {exc}', 'tbears_block_manager') 124 | self._task._block_manager.close() 125 | 126 | 127 | class ChannelTxCreatorInnerTask(object): 128 | def __init__(self, block_manager: 'BlockManager'): 129 | self._block_manager = block_manager 130 | 131 | @message_queue_task 132 | async def create_icx_tx(self, kwargs: dict) -> Tuple[int, Optional[str], str]: 133 | """ 134 | Handler of 'create_icx_tx' message. 'create_icx_tx' is generated by 'icx_sendTransaction' 135 | Validate transaction and enqueue to transaction queue 136 | :param kwargs: transaction data 137 | :return: message code and transaction hash 138 | """ 139 | Logger.debug(f'Get create_tcx_tx message!! {kwargs}', "create_icx_tx") 140 | block_manager = self._block_manager 141 | 142 | # generate tx hash 143 | serialized_data = serialize(kwargs) 144 | tx_hash = create_hash(serialized_data) 145 | 146 | # check duplication 147 | duplicated_tx = False 148 | for tx in block_manager.tx_queue: 149 | if tx_hash == tx['txHash']: 150 | duplicated_tx = True 151 | if duplicated_tx is False and block_manager._block.get_transaction(tx_hash=tx_hash): 152 | duplicated_tx = True 153 | 154 | if duplicated_tx: 155 | return message_code.Response.fail_tx_invalid_duplicated_hash, None, '' 156 | 157 | # check signature validity 158 | signature = kwargs['signature'] 159 | if signature != 'sig': 160 | msg_hash = hashlib.sha3_256(serialized_data).digest() 161 | sig_byte = base64.b64decode(signature) 162 | if not verify_signature(msg_hash, sig_byte, kwargs['from']): 163 | return message_code.Response.fail_tx_invalid_signature, None, '' 164 | 165 | # append to transaction queue 166 | block_manager.add_tx(tx_hash=tx_hash, tx=kwargs) 167 | 168 | Logger.debug(f'Response create_icx_tx!!', "create_icx_tx") 169 | return message_code.Response.success, f"0x{tx_hash}", '' 170 | 171 | 172 | class ChannelTxCreatorService(MessageQueueService[ChannelTxCreatorInnerTask]): 173 | TaskType = ChannelTxCreatorInnerTask 174 | 175 | def _callback_connection_close(self, exc: Exception): 176 | Logger.error(f'[ChannelTxCreatorService] close message queue connection. {exc}', 'tbears_block_manager') 177 | self._task._block_manager.close() 178 | -------------------------------------------------------------------------------- /docs/score_integration_test.md: -------------------------------------------------------------------------------- 1 | # ICON SCORE integration test 2 | 3 | ## Overview 4 | Provide simple explanations for ICON SCORE integration test. 5 | 6 | ## How to Run the SCORE test 7 | ```bash 8 | # with T-Bears command 9 | $ tbears test 10 | 11 | # with python 12 | $ python -m unittest discover 13 | ``` 14 | 15 | ## SCORE project File organization 16 | 17 | | **Item** | **Description** | 18 | | :------------------------- | :----------------------------------------------------------- | 19 | |\ | SCORE project name. Project directory is created with the same name. | 20 | |\/\_\_init\_\_.py | \_\_init\_\_.py file to make the project directory recognized as a python package. | 21 | |\/package.json | Contains the information needed when SCORE is loaded.
"main_module" and "main_class" should be specified. | 22 | |\/.py | SCORE main file. | 23 | |\/tests | Directory for SCORE test code. | 24 | |\/tests/\_\_init\_\_.py | \_\_init\_\_.py file to make the test directory recognized as a python package. | 25 | |\/tests/test\_integrate\_\.py | SCORE integrate test file. | 26 | |\/tests/test\_unit\_\.py | SCORE unit test file. | 27 | 28 | * When T-Bears deploys SCORE, the `tests` directory is not included. 29 | 30 | ## How to write SCORE integration test code 31 | 32 | The SCORE integration test code works as follows 33 | 34 | 1. Deploy SCORE to be tested 35 | 2. Create a ICON JSON-RPC API request for the SCORE API you want to test 36 | 3. If neccessary, sign a ICON JSON-RPC API request 37 | 4. Invoke a ICON JSON-RPC API request and get the result 38 | 5. Check the result 39 | 40 | ### Packages and modules 41 | 42 | #### ICON python SDK 43 | You can create and sign a ICON JSON-RPC API request using the ICON python SDK 44 | 45 | ```python 46 | # create key wallet 47 | self._test = KeyWallet.create() 48 | 49 | # Generates an instance of transaction for deploying SCORE. 50 | transaction = DeployTransactionBuilder() \ 51 | .from_(self._test.get_address()) \ 52 | .to(to) \ 53 | .step_limit(100_000_000_000) \ 54 | .nid(3) \ 55 | .nonce(100) \ 56 | .content_type("application/zip") \ 57 | .content(gen_deploy_data_content(self.SCORE_PROJECT)) \ 58 | .build() 59 | 60 | # Returns the signed transaction object having a signature 61 | signed_transaction = SignedTransaction(transaction, self._test) 62 | ``` 63 | 64 | 65 | 66 | #### IconIntegrateTestBase in T-Bears 67 | > :warning: **WARNING**: ICON Service emulation is not working with IISS. You can stake and delegate but can't get any I-Score for reward. 68 | > If you want to test IISS stuff correctly, set IconIntegrateTestBase.icon_service and send requests to the network. 69 | 70 | Every SCORE integration test class must inherit `IconIntegrateTestBase`. 71 | 72 | IconIntegrateTestBase class provides three functions 73 | 74 | 1. Support python unittest 75 | 76 | 1. You can write and run the test method with prefix 'test_' 77 | 2. You can initalize and finalize the test by override setUp and tearDown method 78 | 79 | 2. Emulate ICON service for test 80 | 1. Initialize ICON service and confirm genesis block 81 | 2. Create accounts for test 82 | 1. self._test1 : Account with 1,000,000 ICX 83 | 2. self._wallet_array[] : 10 empty accounts in list 84 | 85 | 3. Provide API for SCORE integration test 86 | 87 | 1. process_transaction() 88 | 89 | Invoke transaction and return transaction result 90 | 91 | 2. process_call() 92 | 93 | Calls SCORE's external function which is read-only and return result 94 | 95 | ### examples 96 | 97 | You can get source code with `tbears init score_test ScoreTest` command. 98 | 99 | #### score_test.py 100 | 101 | ```python 102 | from iconservice import * 103 | 104 | TAG = 'ScoreTest' 105 | 106 | class ScoreTest(IconScoreBase): 107 | 108 | def __init__(self, db: IconScoreDatabase) -> None: 109 | super().__init__(db) 110 | 111 | def on_install(self) -> None: 112 | super().on_install() 113 | 114 | def on_update(self) -> None: 115 | super().on_update() 116 | 117 | @external(readonly=True) 118 | def hello(self) -> str: 119 | Logger.debug(f'Hello, world!', TAG) 120 | return "Hello" 121 | ``` 122 | 123 | #### score_tests/test_integrate_score_test.py 124 | 125 | ```python 126 | import os 127 | 128 | from iconsdk.builder.call_builder import CallBuilder 129 | from iconsdk.builder.transaction_builder import DeployTransactionBuilder 130 | from iconsdk.libs.in_memory_zip import gen_deploy_data_content 131 | from iconsdk.signed_transaction import SignedTransaction 132 | from tbears.libs.icon_integrate_test import IconIntegrateTestBase, SCORE_INSTALL_ADDRESS 133 | 134 | DIR_PATH = os.path.abspath(os.path.dirname(__file__)) 135 | 136 | 137 | class TestScoreTest(IconIntegrateTestBase): 138 | TEST_HTTP_ENDPOINT_URI_V3 = "http://127.0.0.1:9000/api/v3" 139 | SCORE_PROJECT= os.path.abspath(os.path.join(DIR_PATH, '..')) 140 | 141 | def setUp(self): 142 | super().setUp() 143 | 144 | # WARNING: ICON service emulation is not working with IISS. 145 | # You can stake and delegate but can't get any I-Score for reward. 146 | # If you want to test IISS stuff correctly, set self.icon_service and send requests to the network. 147 | self.icon_service = None 148 | 149 | # If you want to send requests to the network, uncomment next line and set self.TEST_HTTP_ENDPOINT_URI_V3 150 | # self.icon_service = IconService(HTTPProvider(self.TEST_HTTP_ENDPOINT_URI_V3)) 151 | 152 | # install SCORE 153 | self._score_address = self._deploy_score()['scoreAddress'] 154 | 155 | def _deploy_score(self, to: str = SCORE_INSTALL_ADDRESS) -> dict: 156 | # Generates an instance of transaction for deploying SCORE. 157 | transaction = DeployTransactionBuilder() \ 158 | .from_(self._test1.get_address()) \ 159 | .to(to) \ 160 | .step_limit(100_000_000_000) \ 161 | .nid(3) \ 162 | .nonce(100) \ 163 | .content_type("application/zip") \ 164 | .content(gen_deploy_data_content(self.SCORE_PROJECT)) \ 165 | .build() 166 | 167 | # Returns the signed transaction object having a signature 168 | signed_transaction = SignedTransaction(transaction, self._test1) 169 | 170 | # process the transaction in local 171 | tx_result = self.process_transaction(signed_transaction, self.icon_service) 172 | 173 | self.assertEqual(True, tx_result['status']) 174 | self.assertTrue('scoreAddress' in tx_result) 175 | 176 | return tx_result 177 | 178 | def test_score_update(self): 179 | # update SCORE 180 | tx_result = self._deploy_score(self._score_address) 181 | 182 | self.assertEqual(self._score_address, tx_result['scoreAddress']) 183 | 184 | def test_call_hello(self): 185 | # Generates a call instance using the CallBuilder 186 | call = CallBuilder().from_(self._test1.get_address()) \ 187 | .to(self._score_address) \ 188 | .method("hello") \ 189 | .build() 190 | 191 | # Sends the call request 192 | response = self.process_call(call, self.icon_service) 193 | 194 | self.assertEqual("Hello", response) 195 | 196 | ``` 197 | 198 | #### Run test code 199 | 200 | ```bash 201 | $ tbears test score_test 202 | .. 203 | ---------------------------------------------------------------------- 204 | Ran 2 tests in 0.172s 205 | 206 | OK 207 | ``` 208 | 209 | 210 | 211 | ## References 212 | 213 | * [ICON python SDK] (https://repo.theloop.co.kr/icon/icon-sdk/icon-sdk-python) 214 | * [ICON SCORE samples] (https://github.com/icon-project/samples) 215 | 216 | -------------------------------------------------------------------------------- /tbears/command/command_util.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright 2018 ICON Foundation 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | import json 16 | import os 17 | 18 | import IPython 19 | from iconcommons.logger.logger import Logger 20 | from iconsdk.wallet.wallet import KeyWallet 21 | 22 | from tbears.config.tbears_config import ( 23 | TConfigKey, FN_SERVER_CONF, FN_CLI_CONF, tbears_server_config, tbears_cli_config, make_server_config, 24 | FN_KEYSTORE_TEST1, keystore_test1, TBEARS_CLI_TAG, TEST_ACCOUNTS 25 | ) 26 | from tbears.tbears_exception import TBearsCommandException 27 | from tbears.util import ( 28 | get_score_template, get_package_json_dict, write_file, PROJECT_ROOT_PATH 29 | ) 30 | from tbears.util.argparse_type import IconPath 31 | 32 | 33 | class CommandUtil(object): 34 | def __init__(self, subparsers): 35 | self._add_init_parser(subparsers) 36 | self._add_samples_parser(subparsers) 37 | self._add_genconf_parser(subparsers) 38 | self._add_console_parser(subparsers) 39 | 40 | @staticmethod 41 | def _add_init_parser(subparsers) -> None: 42 | parser = subparsers.add_parser('init', help='Initialize tbears project', 43 | description='Initialize SCORE development environment.\n' 44 | 'Generate .py, package.json and test code in ' 45 | ' directory. ' 46 | 'The name of the score class is .') 47 | parser.add_argument('project', type=IconPath('w'), help='Project name') 48 | parser.add_argument('score_class', help='SCORE class name', metavar='scoreClass') 49 | 50 | @staticmethod 51 | def _add_samples_parser(subparsers): 52 | subparsers.add_parser('samples', help='This command has been deprecated since v1.1.0', 53 | description='This command has been deprecated since v1.1.0') 54 | 55 | @staticmethod 56 | def _add_genconf_parser(subparser): 57 | subparser.add_parser('genconf', help=f'Generate tbears config files and keystore files.', 58 | description=f'Generate tbears config files and keystore files.') 59 | 60 | @staticmethod 61 | def _add_console_parser(subparsers): 62 | subparsers.add_parser('console', 63 | help='Get into tbears interactive mode by embedding IPython', 64 | description='Get into tbears interactive mode by embedding IPython') 65 | 66 | def run(self, args): 67 | if not hasattr(self, args.command): 68 | print(f"Wrong command {args.command}") 69 | return 70 | 71 | Logger.info(f"Run '{args.command}' command", TBEARS_CLI_TAG) 72 | 73 | # run command 74 | return getattr(self, args.command)(vars(args)) 75 | 76 | def init(self, conf: dict): 77 | """Initialize the tbears service. 78 | 79 | :param conf: init command configuration 80 | """ 81 | self._check_init(conf) 82 | 83 | # initialize score project package. score class is set using main template. 84 | # you can check main template at util/__init__/get_score_main_template method 85 | self.__initialize_project(project=conf['project'], 86 | score_class=conf['score_class'], 87 | contents_func=get_score_template) 88 | 89 | print(f"Initialized {conf['project']} successfully") 90 | 91 | def samples(self, _conf: dict): 92 | """ Show the information about the sample SCORE 93 | :param _conf: samples command configuration 94 | """ 95 | print("The samples command has been deprecated since v1.1.0") 96 | print("You can check out and download the sample SCORE at https://github.com/icon-project/samples") 97 | 98 | def genconf(self, _conf: dict): 99 | """ Generate tbears config files and keystore files. 100 | """ 101 | result = self.__gen_conf_file() 102 | 103 | if result: 104 | print(f"Made {', '.join(result)} successfully") 105 | else: 106 | print(f"There were configuration files already.") 107 | 108 | def check_command(self, command): 109 | return hasattr(self, command) 110 | 111 | @staticmethod 112 | def _check_init(conf: dict): 113 | if conf['project'] == conf['score_class']: 114 | raise TBearsCommandException(f' and must be different.') 115 | 116 | @staticmethod 117 | def __initialize_project(project: str, score_class: str, contents_func): 118 | """Initialize the tbears project 119 | 120 | :param project: name of tbears project. 121 | :param score_class: class name of SCORE. 122 | :param contents_func contents generator 123 | """ 124 | # make package.json data 125 | package_json_dict = get_package_json_dict(project, score_class) 126 | package_json_contents = json.dumps(package_json_dict, indent=4) 127 | 128 | # when command is init, make score template. 129 | py_contents, test_contents, unit_test_content = contents_func(project, score_class) 130 | 131 | write_file(project, f"{project}.py", py_contents) 132 | write_file(project, "package.json", package_json_contents) 133 | write_file(project, '__init__.py', '') 134 | if len(test_contents): 135 | write_file(f'{project}/tests', f'test_integrate_{project}.py', test_contents) 136 | write_file(f'{project}/tests', f'__init__.py', '') 137 | if len(unit_test_content): 138 | write_file(f"{project}/tests", f'test_unit_{project}.py', unit_test_content) 139 | 140 | @staticmethod 141 | def __gen_conf_file() -> list: 142 | result = [] 143 | 144 | if os.path.exists(FN_CLI_CONF) is False: 145 | result.append(FN_CLI_CONF[2:]) 146 | write_file('./', FN_CLI_CONF, json.dumps(tbears_cli_config, indent=4)) 147 | 148 | if os.path.exists(FN_SERVER_CONF) is False: 149 | result.append(FN_SERVER_CONF[2:]) 150 | server_config_json = make_server_config(tbears_server_config) 151 | write_file('./', FN_SERVER_CONF, json.dumps(server_config_json, indent=4)) 152 | 153 | # mkdir keystore 154 | keystore_dir = "./keystore" 155 | if os.path.exists(keystore_dir) is False: 156 | os.mkdir(keystore_dir) 157 | 158 | # gen keystore files 159 | write_file(keystore_dir, FN_KEYSTORE_TEST1, json.dumps(keystore_test1, indent=4)) 160 | 161 | # keystore file for main P-Rep 162 | main_prep_count = tbears_server_config.get(TConfigKey.PREP_MAIN_PREPS, 0) 163 | for i, prep in enumerate(TEST_ACCOUNTS[:main_prep_count]): 164 | wallet = KeyWallet.load(prep) 165 | wallet.store(file_path=os.path.join(keystore_dir, f"prep{i}_keystore"), 166 | password=f"prep{i}_Account") 167 | 168 | result.append(keystore_dir + "/*") 169 | 170 | return result 171 | 172 | @staticmethod 173 | def get_init_args(project: str, score_class: str): 174 | return { 175 | 'project': project, 176 | 'score_class': score_class 177 | } 178 | 179 | def console(self, conf): 180 | """Get into tbears interactive mode by embedding ipython""" 181 | IPython.start_ipython(['--profile', 'tbears', '--ipython-dir', f'{PROJECT_ROOT_PATH}/tbears']) 182 | -------------------------------------------------------------------------------- /tests/test_parsing_score.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 ICON Foundation 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import json 16 | import os 17 | import shutil 18 | 19 | from tbears.command.command_score import CommandScore, check_project 20 | from tbears.tbears_exception import TBearsCommandException 21 | 22 | from tests.test_parsing_command import TestCommand 23 | from tests.test_util import TEST_UTIL_DIRECTORY 24 | 25 | 26 | class TestCommandScore(TestCommand): 27 | def setUp(self): 28 | super().setUp() 29 | self.tear_down_params = ['proj_unittest'] 30 | 31 | self.project = 'proj_unittest' 32 | self.project_class = 'ProjUnittest' 33 | self.uri = 'http://127.0.0.1:9000/api/v3' 34 | self.mode = "install" 35 | self.arg_from = "hxaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 36 | self.to = "cx0000000000000000000000000000000000000000" 37 | self.keystore = os.path.join(TEST_UTIL_DIRECTORY, 'test_keystore') 38 | self.config_path = os.path.join(TEST_UTIL_DIRECTORY, 'test_tbears_cli_config.json') 39 | 40 | # Test if cli arguments are parsed correctly. 41 | def test_deploy_args_parsing(self): 42 | # Parsing test 43 | os.mkdir(self.project) 44 | cmd = f"deploy {self.project} -u {self.uri} -m {self.mode} -f {self.arg_from} " \ 45 | f"-o {self.to} -k {self.keystore} -c {self.config_path} " 46 | parsed = self.parser.parse_args(cmd.split()) 47 | self.assertEqual(parsed.command, 'deploy') 48 | self.assertEqual(parsed.project, self.project) 49 | self.assertEqual(parsed.uri, self.uri) 50 | self.assertEqual(parsed.mode, self.mode) 51 | self.assertEqual(parsed.to, self.to) 52 | self.assertEqual(parsed.keyStore, self.keystore) 53 | self.assertEqual(parsed.config, self.config_path) 54 | shutil.rmtree(self.project) 55 | 56 | # No project directory or project zip file 57 | cmd = f'deploy {self.project}' 58 | self.assertRaises(SystemExit, self.parser.parse_args, cmd.split()) 59 | 60 | os.mkdir(self.project) 61 | 62 | # Invalid from address 63 | invalid_addr = 'hx1' 64 | cmd = f'deploy {self.project} -f {invalid_addr}' 65 | self.assertRaises(SystemExit, self.parser.parse_args, cmd.split()) 66 | 67 | # Too many arguments 68 | cmd = f'deploy arg1 arg2' 69 | self.assertRaises(SystemExit, self.parser.parse_args, cmd.split()) 70 | 71 | # Insufficient argument 72 | cmd = f'deploy' 73 | self.assertRaises(SystemExit, self.parser.parse_args, cmd.split()) 74 | 75 | # Wrong option 76 | cmd = f'deploy {self.project} -w wrongoption' 77 | self.assertRaises(SystemExit, self.parser.parse_args, cmd.split()) 78 | 79 | # Not supported mode (only install, update are available) 80 | cmd = f'deploy {self.project} -m not_supported_mode' 81 | self.assertRaises(SystemExit, self.parser.parse_args, cmd.split()) 82 | 83 | # Invalid to address 84 | invalid_addr = 'hx1' 85 | cmd = f'deploy {self.project} -o {invalid_addr}' 86 | self.assertRaises(SystemExit, self.parser.parse_args, cmd.split()) 87 | 88 | # Keystore file does not exist 89 | cmd = f'deploy {self.project} -k ./keystore_not_exist' 90 | self.assertRaises(SystemExit, self.parser.parse_args, cmd.split()) 91 | 92 | # config file does not exist 93 | cmd = f'deploy {self.project} -c ./config_not_exist' 94 | self.assertRaises(SystemExit, self.parser.parse_args, cmd.split()) 95 | 96 | shutil.rmtree(self.project) 97 | 98 | # Deploy method (deploy, _check_deploy) test. before deploy score, 99 | # Check if arguments satisfy requirements. 100 | # bug: when test this method in terminal, no error found, but in pycharm Run Test, it raise error 101 | def test_check_deploy_necessary_args(self): 102 | # # Deploy essential check 103 | # No project directory 104 | cmd = f'deploy {self.project}' 105 | self.assertRaises(SystemExit, self.parser.parse_args, cmd.split()) 106 | 107 | # Keystore file does not exist 108 | no_keystore = './keystore_not_exist' 109 | cmd = f'deploy {self.project} -k {no_keystore}' 110 | self.assertRaises(SystemExit, self.parser.parse_args, cmd.split()) 111 | 112 | conf = self.cmd.cmdUtil.get_init_args(project=self.project, score_class=self.project_class) 113 | self.cmd.cmdUtil.init(conf) 114 | 115 | # Invalid password value 116 | # Even though input invalid password, _check_deploy method should return password 117 | # (this method doesn't check password value) 118 | cmd = f'deploy {self.project} -k {self.keystore}' 119 | user_input_password = "1234" 120 | expected_password = "1234" 121 | parsed = self.parser.parse_args(cmd.split()) 122 | self.assertEqual(CommandScore._check_deploy(vars(parsed), user_input_password), expected_password) 123 | 124 | # Insufficient argument 125 | cmd = f'deploy {self.project} -m update' 126 | parsed = self.parser.parse_args(cmd.split()) 127 | self.assertRaises(TBearsCommandException, CommandScore._check_deploy, vars(parsed)) 128 | 129 | shutil.rmtree(self.project) 130 | 131 | def test_check_deploy_project(self): 132 | conf = self.cmd.cmdUtil.get_init_args(project=self.project, score_class=self.project_class) 133 | self.cmd.cmdUtil.init(conf) 134 | 135 | project = f"{self.project}" 136 | 137 | # there is no __init__.py 138 | os.rename(f"{project}/__init__.py", "__init__.py.bak") 139 | self.assertRaises(TBearsCommandException, check_project, project) 140 | os.rename("__init__.py.bak", f"{project}/__init__.py") 141 | 142 | # there is no package.json 143 | os.rename(f"{project}/package.json", "package.json.bak") 144 | self.assertRaises(TBearsCommandException, check_project, project) 145 | 146 | # wrong package.json file 147 | self.touch(f"{project}/package.json") 148 | self.assertRaises(TBearsCommandException, check_project, project) 149 | os.rename("package.json.bak", f"{project}/package.json") 150 | 151 | # there is no main_module file 152 | os.rename(f"{project}/{project}.py", f"{project}.py.bak") 153 | self.assertRaises(TBearsCommandException, check_project, project) 154 | 155 | # working good 156 | os.rename(f"{project}.py.bak", f"{project}/{project}.py") 157 | self.assertEqual(check_project(project), 0) 158 | 159 | # do not allow '/' in main_module field 160 | os.mkdir(f"{project}/modify") 161 | os.rename(f"{project}/{project}.py", f"{project}/modify/{project}.py") 162 | with open(f"{project}/package.json", mode='r+') as file: 163 | package: dict = json.load(file) 164 | package['main_module'] = f"modify/{project}" 165 | file.seek(0) 166 | file.truncate() 167 | json.dump(package, file) 168 | self.assertRaises(TBearsCommandException, check_project, project) 169 | 170 | # allow '.' in main_module field 171 | with open(f"{project}/package.json", mode='r+') as file: 172 | package: dict = json.load(file) 173 | package['main_module'] = f"modify.{project}" 174 | file.seek(0) 175 | file.truncate() 176 | json.dump(package, file) 177 | self.assertEqual(check_project(project), 0) 178 | 179 | def test_clear_args_parsing(self): 180 | # Parsing test 181 | cmd = f'clear' 182 | parsed = self.parser.parse_args(cmd.split()) 183 | self.assertEqual(parsed.command, 'clear') 184 | 185 | # Too many arguments 186 | cmd = f'clear arg1 arg2' 187 | self.assertRaises(SystemExit, self.parser.parse_args, cmd.split()) 188 | -------------------------------------------------------------------------------- /tests/test_block.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright 2017-2018 ICON Foundation 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | import json 16 | import os 17 | import shutil 18 | import time 19 | import unittest 20 | 21 | from tbears.block_manager.block import Block 22 | from tbears.block_manager.block_manager import BlockManager 23 | from tbears.config.tbears_config import tbears_server_config 24 | 25 | 26 | class TestTBearsBlock(unittest.TestCase): 27 | DB_PATH = './testdb' 28 | PREV_BLOCK_HASH = '0123456789abcdef' 29 | 30 | def setUp(self): 31 | config = tbears_server_config 32 | config['stateDbRootPath'] = self.DB_PATH 33 | self.block_manager = BlockManager(config) 34 | self.block = self.block_manager.block 35 | 36 | def tearDown(self): 37 | try: 38 | if os.path.exists(self.DB_PATH): 39 | shutil.rmtree(self.DB_PATH) 40 | except: 41 | pass 42 | 43 | def test_property(self): 44 | # block_height 45 | block_height = self.block.block_height 46 | self.assertEqual(-1, block_height) 47 | self.block.increase_block_height() 48 | self.assertEqual(block_height + 1, self.block.block_height) 49 | 50 | # prev_block_hash 51 | self.assertEqual(None, self.block.prev_block_hash) 52 | self.block.set_prev_block_hash(self.PREV_BLOCK_HASH) 53 | self.assertEqual(self.PREV_BLOCK_HASH, self.block.prev_block_hash) 54 | 55 | # reload Block info 56 | self.block.db.close() 57 | self.block = Block(db_path=f"{self.DB_PATH}/tbears/") 58 | self.assertEqual(block_height + 1, self.block.block_height) 59 | self.assertEqual(self.PREV_BLOCK_HASH, self.block.prev_block_hash) 60 | 61 | def test_commit_block(self): 62 | block_height = self.block.block_height 63 | self.block.commit_block(self.PREV_BLOCK_HASH) 64 | self.assertEqual(block_height + 1, self.block.block_height) 65 | self.assertEqual(self.PREV_BLOCK_HASH, self.block.prev_block_hash) 66 | 67 | def test_transactions(self): 68 | tx_list = [ 69 | {'txHash': '01'}, 70 | {'txHash': '02'}, 71 | {'txHash': '03'} 72 | ] 73 | self.block.save_transactions(tx_list, self.PREV_BLOCK_HASH) 74 | 75 | for i, v in enumerate(tx_list): 76 | tx = self.block.get_transaction(v.get('txHash')) 77 | self.assertEqual(v, tx.get('transaction')) 78 | self.assertEqual(hex(i), tx.get('tx_index')) 79 | self.assertEqual(hex(self.block.block_height + 1), tx.get('block_height')) 80 | self.assertEqual(f'0x{self.PREV_BLOCK_HASH}', tx.get('block_hash')) 81 | 82 | def test_txresult(self): 83 | tx_result = {'key': 'value'} 84 | tx_hash = '0123' 85 | 86 | self.block.save_txresult(tx_hash, tx_result) 87 | result = self.block.get_txresult(tx_hash) 88 | self.assertEqual(tx_result, json.loads(result)) 89 | 90 | tx_list = [ 91 | {'txHash': '01'}, 92 | {'txHash': '02'}, 93 | {'txHash': '03'} 94 | ] 95 | tx_results = [ 96 | {'txHash': '01', 'value': 1, 'blockHash': "test_block_hash"}, 97 | {'txHash': '02', 'value': 2, 'blockHash': "test_block_hash"}, 98 | {'txHash': '03', 'value': 3, 'blockHash': "test_block_hash"}, 99 | ] 100 | 101 | new_block_hash = "new_block_hash" 102 | self.block.save_txresults(tx_results, new_block_hash) 103 | for i, v in enumerate(tx_list): 104 | result = self.block.get_txresult(v.get('txHash')) 105 | result_dict = json.loads(result) 106 | self.assertEqual(v.get('txHash'), result_dict.get('txHash')) 107 | self.assertEqual(tx_results[i].get('value'), result_dict.get('value')) 108 | self.assertEqual(new_block_hash, result_dict.get('blockHash')) 109 | 110 | def test_block(self): 111 | test_address = tbears_server_config['genesis']['accounts'][2]['address'] 112 | # genesis block 113 | timestamp = int(time.time() * 10 ** 6) 114 | invoke_response = { 115 | "txResults": {}, 116 | "stateRootHash": "1"*64, 117 | "hash": self.PREV_BLOCK_HASH 118 | } 119 | 120 | self.block_manager._prep_manager.get_prev_block_contributors_info() 121 | block = self.block_manager._make_block_data(self.PREV_BLOCK_HASH, tbears_server_config['genesis'], 122 | timestamp, invoke_response) 123 | self.block.save_block(block) 124 | self.block.commit_block(self.PREV_BLOCK_HASH) 125 | 126 | self._check_block(self.PREV_BLOCK_HASH, '', tbears_server_config['genesis'], timestamp, 0, 127 | test_address, test_address, is_genesis=True) 128 | 129 | # normal block 130 | tx_list = [ 131 | {'txHash': '01'}, 132 | {'txHash': '02'}, 133 | {'txHash': '03'} 134 | ] 135 | timestamp = int(time.time() * 10 ** 6) 136 | block_hash = self.PREV_BLOCK_HASH + '01' 137 | invoke_response = { 138 | "txResults": [], 139 | "stateRootHash": "0"*64, 140 | "hash": block_hash, 141 | } 142 | 143 | block = self.block_manager._make_block_data(block_hash, tx_list, timestamp, invoke_response) 144 | self.block.save_block(block) 145 | self.block.commit_block(block_hash) 146 | 147 | self._check_block(block_hash, self.PREV_BLOCK_HASH, tx_list, timestamp, 1, test_address, test_address) 148 | 149 | tx_list = [ 150 | {'txHash': '011'}, 151 | {'txHash': '022'}, 152 | {'txHash': '033'} 153 | ] 154 | timestamp = int(time.time() * 10 ** 6) 155 | block_hash = self.PREV_BLOCK_HASH + '02' 156 | invoke_response = { 157 | "txResults": [], 158 | "stateRootHash": "0" * 64, 159 | "hash": block_hash, 160 | } 161 | 162 | block = self.block_manager._make_block_data(block_hash, tx_list, timestamp, invoke_response) 163 | self.block.save_block(block) 164 | self.block.commit_block(block_hash) 165 | 166 | def _check_block(self, block_hash, prev_hash, tx_list, timestamp, height, leader, next_leader, is_genesis=False): 167 | last_block = self.block.get_last_block() 168 | block_by_hash = self.block.get_block_by_hash(block_hash) 169 | self.assertEqual(block_by_hash, last_block) 170 | self.assertEqual('tbears', block_by_hash.get('version')) 171 | self.assertEqual(prev_hash, block_by_hash.get('prevHash')) 172 | self.assertEqual(timestamp, block_by_hash.get('timestamp')) 173 | self.assertEqual(block_hash, block_by_hash.get('hash')) 174 | self.assertEqual(height, block_by_hash.get('height')) 175 | self.assertEqual(leader, block_by_hash.get('leader')) 176 | self.assertEqual(next_leader, block_by_hash.get('nextLeader')) 177 | self.assertIn("transactionsHash", block_by_hash) 178 | self.assertIn("receiptsHash", block_by_hash) 179 | self.assertIn("repsHash", block_by_hash) 180 | self.assertIn("nextRepsHash", block_by_hash) 181 | self.assertIn("leaderVotesHash", block_by_hash) 182 | self.assertIn("prevVotesHash", block_by_hash) 183 | self.assertIn("logsBloom", block_by_hash) 184 | self.assertIn("leaderVotes", block_by_hash) 185 | self.assertIn("prevVotes", block_by_hash) 186 | if is_genesis: 187 | self.assertEqual(tx_list, block_by_hash.get('transactions')[0]) 188 | self.assertEqual("", block_by_hash.get('signature')) 189 | else: 190 | self.assertEqual(tx_list, block_by_hash.get('transactions')) 191 | self.assertEqual("tbears_block_manager_does_not_support_block_signature", block_by_hash.get('signature')) 192 | -------------------------------------------------------------------------------- /tbears/util/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright 2018 ICON Foundation 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | import os 16 | import re 17 | import hashlib 18 | 19 | import pkg_resources 20 | 21 | from ..tbears_exception import TBearsWriteFileException 22 | 23 | 24 | DIR_PATH = os.path.abspath(os.path.dirname(__file__)) 25 | PROJECT_ROOT_PATH = os.path.abspath(os.path.join(DIR_PATH, '..', '..')) 26 | 27 | 28 | def write_file(parent_directory: str, file_name: str, contents: str, overwrite: bool = False) -> None: 29 | """Create file with the contents in the parents directory. 30 | 31 | :param parent_directory: Location to create the file. 32 | :param file_name: File name 33 | :param contents: Contents of file. 34 | """ 35 | try: 36 | if not os.path.exists(parent_directory): 37 | os.makedirs(parent_directory) 38 | if os.path.exists(f'{parent_directory}/{file_name}') and not overwrite: 39 | return 40 | with open(f'{parent_directory}/{file_name}', mode='w') as file: 41 | file.write(contents) 42 | except (PermissionError, IsADirectoryError) as e: 43 | raise TBearsWriteFileException(f"Can't write file {parent_directory}/{file_name}. {e}") 44 | 45 | 46 | def get_score_template(score_project: str, score_class: str) -> tuple: 47 | """ 48 | :param score_project: Your project name. 49 | :param score_class: Your score class name. 50 | :return: 51 | """ 52 | main_py = """from iconservice import * 53 | 54 | TAG = 'SampleScore' 55 | 56 | 57 | class SampleScore(IconScoreBase): 58 | 59 | def __init__(self, db: IconScoreDatabase) -> None: 60 | super().__init__(db) 61 | 62 | def on_install(self) -> None: 63 | super().on_install() 64 | 65 | def on_update(self) -> None: 66 | super().on_update() 67 | 68 | @external(readonly=True) 69 | def hello(self) -> str: 70 | Logger.debug(f'Hello, world!', TAG) 71 | return "Hello" 72 | """ 73 | 74 | test_py = """import os 75 | 76 | from iconsdk.builder.call_builder import CallBuilder 77 | from iconsdk.builder.transaction_builder import DeployTransactionBuilder 78 | from iconsdk.libs.in_memory_zip import gen_deploy_data_content 79 | from iconsdk.signed_transaction import SignedTransaction 80 | from tbears.libs.icon_integrate_test import IconIntegrateTestBase, SCORE_INSTALL_ADDRESS 81 | 82 | DIR_PATH = os.path.abspath(os.path.dirname(__file__)) 83 | 84 | 85 | class TestTest(IconIntegrateTestBase): 86 | TEST_HTTP_ENDPOINT_URI_V3 = "http://127.0.0.1:9000/api/v3" 87 | SCORE_PROJECT = os.path.abspath(os.path.join(DIR_PATH, '..')) 88 | 89 | def setUp(self): 90 | super().setUp() 91 | 92 | # WARNING: ICON service emulation is not working with IISS. 93 | # You can stake and delegate but can't get any I-Score for reward. 94 | # If you want to test IISS stuff correctly, set self.icon_service and send requests to the network 95 | self.icon_service = None 96 | 97 | # If you want to send requests to the network, uncomment next line and set self.TEST_HTTP_ENDPOINT_URI_V3 98 | # self.icon_service = IconService(HTTPProvider(self.TEST_HTTP_ENDPOINT_URI_V3)) 99 | 100 | # install SCORE 101 | self._score_address = self._deploy_score()['scoreAddress'] 102 | 103 | def _deploy_score(self, to: str = SCORE_INSTALL_ADDRESS) -> dict: 104 | # Generates an instance of transaction for deploying SCORE. 105 | transaction = DeployTransactionBuilder() \ 106 | .from_(self._test1.get_address()) \ 107 | .to(to) \ 108 | .step_limit(100_000_000_000) \ 109 | .nid(3) \ 110 | .nonce(100) \ 111 | .content_type("application/zip") \ 112 | .content(gen_deploy_data_content(self.SCORE_PROJECT)) \ 113 | .build() 114 | 115 | # Returns the signed transaction object having a signature 116 | signed_transaction = SignedTransaction(transaction, self._test1) 117 | 118 | # process the transaction in local 119 | tx_result = self.process_transaction(signed_transaction, self.icon_service) 120 | 121 | self.assertEqual(True, tx_result['status']) 122 | self.assertTrue('scoreAddress' in tx_result) 123 | 124 | return tx_result 125 | 126 | def test_score_update(self): 127 | # update SCORE 128 | tx_result = self._deploy_score(self._score_address) 129 | 130 | self.assertEqual(self._score_address, tx_result['scoreAddress']) 131 | 132 | def test_call_hello(self): 133 | # Generates a call instance using the CallBuilder 134 | call = CallBuilder().from_(self._test1.get_address()) \ 135 | .to(self._score_address) \ 136 | .method("hello") \ 137 | .build() 138 | 139 | # Sends the call request 140 | response = self.process_call(call, self.icon_service) 141 | 142 | self.assertEqual("Hello", response) 143 | """ 144 | test_unit_py = """from ..sample_score import SampleScore 145 | from tbears.libs.scoretest.score_test_case import ScoreTestCase 146 | 147 | 148 | class TestSampleScore(ScoreTestCase): 149 | 150 | def setUp(self): 151 | super().setUp() 152 | self.score = self.get_score_instance(SampleScore, self.test_account1) 153 | 154 | def test_hello(self): 155 | self.assertEqual(self.score.hello(), "Hello") 156 | """ 157 | return main_py.replace("SampleScore", score_class), test_py.replace("SampleScore", score_class), test_unit_py.\ 158 | replace("sample_score", score_project).replace("SampleScore", score_class) 159 | 160 | 161 | def get_package_json_dict(project: str, score_class: str) -> dict: 162 | """Returns the template of package.json 163 | 164 | :param project: SCORE's name. 165 | :param score_class: SCORE's main class name. 166 | 167 | :return: package.json's contents.(dict) 168 | """ 169 | package_json_dict = { 170 | "version": "0.0.1", 171 | "main_module": f"{project}", 172 | "main_score": f"{score_class}" 173 | } 174 | return package_json_dict 175 | 176 | 177 | def is_lowercase_hex_string(value: str) -> bool: 178 | """Check whether value is hexadecimal format or not 179 | 180 | :param value: text 181 | :return: True(lowercase hexadecimal) otherwise False 182 | """ 183 | try: 184 | result = re.match('[0-9a-f]+', value) 185 | return len(result.group(0)) == len(value) 186 | except: 187 | pass 188 | 189 | return False 190 | 191 | 192 | def create_hash(data: bytes) -> str: 193 | return f'{hashlib.sha3_256(data).hexdigest()}' 194 | 195 | 196 | def is_valid_hash(_hash: str) -> bool: 197 | """Check hash is valid. 198 | 199 | :param _hash: 200 | :return: 201 | """ 202 | if isinstance(_hash, str) and len(_hash) == 66: 203 | prefix, body = _hash[:2], _hash[2:] 204 | return prefix == '0x' and is_lowercase_hex_string(body) 205 | 206 | return False 207 | 208 | 209 | def get_tbears_version() -> str: 210 | """Get version of tbears. 211 | The location of the file that holds the version information is different when packaging and when executing. 212 | :return: version of tbears. 213 | """ 214 | try: 215 | version = pkg_resources.get_distribution('tbears').version 216 | except pkg_resources.DistributionNotFound: 217 | version_path = os.path.join(PROJECT_ROOT_PATH, 'VERSION') 218 | with open(version_path, mode='r') as version_file: 219 | version = version_file.read() 220 | except: 221 | version = 'unknown' 222 | return version 223 | 224 | 225 | def jsonrpc_params_to_pep_style(params: dict): 226 | change_dict_key_name(params, 'from', 'from_') 227 | change_dict_key_name(params, 'stepLimit', 'step_limit') 228 | change_dict_key_name(params, 'dataType', 'data_type') 229 | 230 | 231 | def change_dict_key_name(params: dict, origin_name: str, new_name: str): 232 | if origin_name in params: 233 | params[new_name] = params.pop(origin_name) 234 | -------------------------------------------------------------------------------- /tbears/libs/scoretest/score_test_case.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright 2018 ICON Foundation 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | from typing import TypeVar, Type, Optional, List 17 | from unittest import TestCase 18 | from unittest.mock import Mock, patch 19 | 20 | from iconservice import IconScoreBase 21 | from iconservice.base.address import Address 22 | from iconservice.base.exception import InvalidRequestException 23 | from iconservice.iconscore.icon_score_base2 import PRepInfo 24 | 25 | from .mock.icx_engine import IcxEngine 26 | from .patch.context import Context, get_icon_score, clear_data 27 | from .patch.score_patcher import ScorePatcher, create_address, get_interface_score, start_SCORE_APIs_patch 28 | 29 | T = TypeVar('T') 30 | 31 | 32 | def validate_score(score): 33 | if issubclass(score, IconScoreBase) is False: 34 | raise InvalidRequestException(f"{score.__name__} is invalid SCORE class") 35 | 36 | 37 | def validate_score_instance(score): 38 | if isinstance(score, IconScoreBase) is False: 39 | raise InvalidRequestException(f"{score.__name__} is invalid SCORE") 40 | 41 | 42 | class ScoreTestCase(TestCase): 43 | 44 | @classmethod 45 | def setUpClass(cls): 46 | ScorePatcher.start_patches() 47 | 48 | @classmethod 49 | def tearDownClass(cls): 50 | ScorePatcher.stop_patches() 51 | 52 | def setUp(self): 53 | self.genesis_address = create_address() 54 | self.test_account1 = create_address() 55 | self.test_account2 = create_address() 56 | account_info = {self.genesis_address: 10**30, 57 | self.test_account1: 10**21, 58 | self.test_account2: 10**21} 59 | ScoreTestCase.initialize_accounts(account_info) 60 | 61 | def tearDown(self): 62 | Context.reset_context() 63 | clear_data() 64 | 65 | @staticmethod 66 | def get_score_instance(score_class: Type[T], owner: 'Address', on_install_params: dict = {}, 67 | score_address: Optional[Address] = None) -> T: 68 | """Get an instance of the SCORE class passed as an score_class arguments 69 | 70 | :param score_class: SCORE class to instantiate 71 | :param owner: Address to set as owner of SCORE 72 | :param on_install_params: To be passed to the SCORE on_install method 73 | :param score_address: score address 74 | :return: Initialized SCORE 75 | """ 76 | validate_score(score_class) 77 | score_db = ScorePatcher.get_score_db(score_address) 78 | score = ScorePatcher.initialize_score(score_class, score_db, owner) 79 | setattr(score, "call", Mock()) 80 | score.on_install(**on_install_params) 81 | ScoreTestCase.set_msg(None, None) 82 | start_SCORE_APIs_patch(score_class.__module__) 83 | return score 84 | 85 | @staticmethod 86 | def update_score(prev_score_address: 'Address', score_class: Type[T], on_update_params: dict = {}) -> T: 87 | """Update SCORE at 'prev_score_address' with 'score_class' instance and get updated SCORE 88 | 89 | :param prev_score_address: address of SCORE to update 90 | :param score_class: SCORE class to update 91 | :param on_update_params: To be passed to the SCORE on_update method 92 | :return: Updated SCORE 93 | """ 94 | validate_score(score_class) 95 | prev_score = get_icon_score(prev_score_address) 96 | score_db = ScorePatcher.get_score_db(prev_score.address) 97 | score = ScorePatcher.initialize_score(score_class, score_db, prev_score.owner) 98 | score.on_update(**on_update_params) 99 | ScoreTestCase.set_msg(None, None) 100 | return score 101 | 102 | @staticmethod 103 | def set_msg(sender: Optional['Address'] = None, value: int = 0): 104 | """Set msg property used inside SCORE 105 | 106 | :param sender: sender attribute of msg 107 | :param value: value attribute of msg 108 | """ 109 | Context.set_msg(sender, value) 110 | 111 | @staticmethod 112 | def set_tx(origin: Optional['Address'] = None, timestamp: Optional[int] = None, _hash: bytes = None, 113 | index: int = 0, nonce: int = 0): 114 | """Set tx property used inside SCORE 115 | 116 | :param origin: origin attribute of tx 117 | :param timestamp: timestamp attribute of tx 118 | :param _hash: hash attribute of tx 119 | :param index: index attribute of tx 120 | :param nonce: nonce attribute of tx 121 | """ 122 | Context.set_tx(origin, timestamp, _hash, index, nonce) 123 | 124 | @staticmethod 125 | def set_block(height: int = 0, timestamp: Optional[int] = None): 126 | """Sets block property used inside SCORE 127 | 128 | :param height: height attribute of block 129 | :param timestamp: timestamp attribute of block 130 | """ 131 | Context.set_block(height, timestamp) 132 | 133 | @staticmethod 134 | def register_interface_score(internal_score_address): 135 | """Register interface SCORE. This method must be called before testing internal call 136 | 137 | :param internal_score_address: address of interface SCORE 138 | """ 139 | ScorePatcher.register_interface_score(internal_score_address) 140 | 141 | @staticmethod 142 | def patch_internal_method(internal_score_address, method, new_method=lambda: None): 143 | """Patch method of SCORE on internal_score_address with given method 144 | 145 | This method call register_interface_score method. so, don't need to call register_interface_score method 146 | if this method called. 147 | 148 | :param internal_score_address: address of the SCORE having method to be called 149 | :param method: method to be patched 150 | :param new_method: method to patch 151 | """ 152 | ScorePatcher.register_interface_score(internal_score_address) 153 | ScorePatcher.patch_internal_method(internal_score_address, method, new_method) 154 | 155 | @staticmethod 156 | def patch_call(score_instance, new_method=lambda: None): 157 | """Patch IconScoreBase.call method. This method must be called if score.call querying other SCORE's method 158 | 159 | :param score_instance: score instance to patch 160 | :param new_method: method to patch 161 | """ 162 | setattr(score_instance, "call", Mock(side_effect=new_method)) 163 | 164 | @staticmethod 165 | def assert_internal_call(internal_score_address, method, *params): 166 | """Assert internal call was called with the specified arguments. 167 | 168 | Raises an AssertionError if the params passed in are 169 | different to the last call to the mock. 170 | 171 | :param internal_score_address: address of internal call SCORE 172 | :param method: method to check 173 | :param params: params to check 174 | """ 175 | interface_score = get_interface_score(internal_score_address) 176 | internal_method = getattr(interface_score, method) 177 | internal_method.assert_called_with(*params) 178 | 179 | @staticmethod 180 | def transfer(_from: 'Address', to: 'Address', amount: int): 181 | """Transfer icx to given 'to' address 182 | 183 | :param _from: address of sender 184 | :param to: address of receiver 185 | :param amount: amount to transfer 186 | """ 187 | if to.is_contract: 188 | context = Context.get_context() 189 | sender = context.msg.sender 190 | value = context.msg.value 191 | Context.set_msg(_from, amount) 192 | score = get_icon_score(to) 193 | score.fallback() 194 | Context.set_msg(sender, value) 195 | else: 196 | IcxEngine.transfer(None, _from, to, amount) 197 | 198 | @staticmethod 199 | def get_balance(address: 'Address'): 200 | """Query icx balance of given address 201 | 202 | :param address: address to query for icx balance 203 | :return: icx balance of given address 204 | """ 205 | return IcxEngine.get_balance(None, address) 206 | 207 | @staticmethod 208 | def initialize_accounts(accounts_info: dict): 209 | """Initialize accounts using given dictionary info 210 | 211 | :param accounts_info: dictionary with address as key and balance as value 212 | """ 213 | for account, amount in accounts_info.items(): 214 | IcxEngine.db.put(None, account.to_bytes(), amount) 215 | 216 | @staticmethod 217 | def patch_main_preps(score, prep_info_list: List[PRepInfo], term_end_block: int): 218 | validate_score_instance(score) 219 | patch(f"{score.__module__}.get_main_prep_info", return_value=(prep_info_list, term_end_block)).start() 220 | 221 | @staticmethod 222 | def patch_sub_preps(score, prep_info_list: List[PRepInfo], term_end_block: int): 223 | validate_score_instance(score) 224 | patch(f"{score.__module__}.get_sub_prep_info", return_value=(prep_info_list, term_end_block)).start() 225 | 226 | @staticmethod 227 | def create_preps_info(prep_count: int): 228 | preps = [] 229 | for index in range(prep_count): 230 | PRepInfo(create_address(), 0, f"PREP{index}") 231 | preps.append(PRepInfo) 232 | return preps 233 | -------------------------------------------------------------------------------- /tests/test_command_wallet.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2019 ICON Foundation 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | import os 18 | import shutil 19 | import socket 20 | import time 21 | import unittest 22 | 23 | from iconcommons.icon_config import IconConfig 24 | 25 | from tbears.command.command import Command 26 | from tbears.command.command_server import TBEARS_CLI_ENV 27 | from tbears.config.tbears_config import FN_SERVER_CONF, FN_CLI_CONF, tbears_server_config 28 | 29 | from tests.test_util import TEST_UTIL_DIRECTORY 30 | 31 | 32 | class TestCommandWallet(unittest.TestCase): 33 | def setUp(self): 34 | self.cmd = Command() 35 | # start 36 | tbears_config_path = os.path.join(TEST_UTIL_DIRECTORY, f'test_tbears_server_config.json') 37 | self.start_conf = IconConfig(tbears_config_path, tbears_server_config) 38 | self.start_conf.load() 39 | self.start_conf['config'] = tbears_config_path 40 | self.start_conf = self.start_conf 41 | self.cmd.cmdServer.start(self.start_conf) 42 | self.assertTrue(self.cmd.cmdServer.is_service_running()) 43 | 44 | def tearDown(self): 45 | # stop 46 | self.cmd.cmdServer.stop(None) 47 | self.assertFalse(self.check_server()) 48 | self.assertTrue(os.path.exists(TBEARS_CLI_ENV)) 49 | 50 | # clear 51 | self.cmd.cmdScore.clear(self.start_conf) 52 | self.assertFalse(os.path.exists(self.start_conf['scoreRootPath'])) 53 | self.assertFalse(os.path.exists(self.start_conf['stateDbRootPath'])) 54 | self.assertFalse(os.path.exists(TBEARS_CLI_ENV)) 55 | try: 56 | if os.path.exists(FN_CLI_CONF): 57 | os.remove(FN_CLI_CONF) 58 | if os.path.exists(FN_SERVER_CONF): 59 | os.remove(FN_SERVER_CONF) 60 | if os.path.exists('./tbears.log'): 61 | os.remove('./tbears.log') 62 | self.cmd.cmdServer.stop(None) 63 | if os.path.exists('exc'): 64 | shutil.rmtree('exc') 65 | self.cmd.cmdScore.clear(self.start_conf if self.start_conf else tbears_server_config) 66 | except: 67 | pass 68 | 69 | @staticmethod 70 | def check_server(): 71 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 72 | # if socket is connected, the result code is 0 (false). 73 | result = sock.connect_ex(('127.0.0.1', 9000)) 74 | sock.close() 75 | return result == 0 76 | 77 | def tx_command(self, command: str, conf: dict, password: str = None) -> dict: 78 | conf['password'] = password 79 | command_handler = getattr(self.cmd.cmdWallet, command) 80 | response = command_handler(conf=conf) 81 | # Wait until block_manager confirm block. block_manager for test confirm every second 82 | time.sleep(2) 83 | return response 84 | 85 | def test_transfer_command(self): 86 | # transfer success #1 (without stepLimit) 87 | args = {"to": f"hx{'a'*40}", "value": 1, "from": "hxef73db5d0ad02eb1fadb37d0041be96bfa56d4e6"} 88 | conf = self.cmd.cmdWallet.get_icon_conf(command='transfer', args=args) 89 | response = self.tx_command("transfer", conf=conf) 90 | self.assertEqual(response.get('error', False), False) 91 | # check result 92 | tx_hash = response['result'] 93 | conf = self.cmd.cmdWallet.get_icon_conf('txresult', {'hash': tx_hash}) 94 | transaction_result_response = self.cmd.cmdWallet.txresult(conf) 95 | self.assertFalse(transaction_result_response.get('error', False)) 96 | 97 | # transfer success #2 (without stepLimit, with keystore) 98 | args = {"to": f"hx{'a'*40}", "value": 1, "from": "hxef73db5d0ad02eb1fadb37d0041be96bfa56d4e6", 99 | "keyStore": os.path.join(TEST_UTIL_DIRECTORY, 'test_keystore')} 100 | conf = self.cmd.cmdWallet.get_icon_conf(command='transfer', args=args) 101 | response = self.tx_command("transfer", conf=conf, password='qwer1234%') 102 | self.assertEqual(response.get('error', False), False) 103 | # check result 104 | tx_hash = response['result'] 105 | conf = self.cmd.cmdWallet.get_icon_conf('txresult', {'hash': tx_hash}) 106 | transaction_result_response = self.cmd.cmdWallet.txresult(conf) 107 | self.assertFalse(transaction_result_response.get('error', False)) 108 | 109 | # transfer fail #1 (apply stepLimit config with command line argument) 110 | args = {"stepLimit": "0x1", "to": f"hx{'a'*40}", "value": 1, 111 | "from": "hxef73db5d0ad02eb1fadb37d0041be96bfa56d4e6"} 112 | conf = self.cmd.cmdScore.get_icon_conf(command='transfer', args=args) 113 | response = self.tx_command("transfer", conf=conf) 114 | self.assertIsInstance(response.get('error', False), dict) 115 | 116 | # transfer fail #2 (apply stepLimit config with config file) 117 | tbears_cli_config_step_set_path = os.path.join(TEST_UTIL_DIRECTORY, 'test_tbears_cli_config_step_set.json') 118 | args = {"config": tbears_cli_config_step_set_path, "to": f"hx{'a'*40}", "value": 1, 119 | "from": "hxef73db5d0ad02eb1fadb37d0041be96bfa56d4e6"} 120 | conf = self.cmd.cmdScore.get_icon_conf(command='transfer', args=args) 121 | response = self.tx_command("transfer", conf=conf) 122 | self.assertIsInstance(response.get('error', False), dict) 123 | 124 | def test_sendtx_command(self): 125 | # use the stepLimit in the json file 126 | send_json_path = os.path.join(TEST_UTIL_DIRECTORY, 'send.json') 127 | args = {"json_file": send_json_path} 128 | conf = self.cmd.cmdWallet.get_icon_conf(command='sendtx', args=args) 129 | response = self.tx_command("sendtx", conf=conf) 130 | self.assertEqual(response.get('error', False), False) 131 | # check result 132 | tx_hash = response['result'] 133 | conf = self.cmd.cmdWallet.get_icon_conf('txresult', {'hash': tx_hash}) 134 | transaction_result_response = self.cmd.cmdWallet.txresult(conf) 135 | self.assertFalse(transaction_result_response.get('error', False)) 136 | # check the stepLimit in the confirmed tx 137 | confirmed_transaction = self.cmd.cmdWallet.txbyhash(conf) 138 | self.assertTrue(confirmed_transaction['result']['stepLimit'], '0x3000000') 139 | 140 | # use the stepLimit in the json file, with keystore 141 | args["keyStore"] = os.path.join(TEST_UTIL_DIRECTORY, 'test_keystore') 142 | conf = self.cmd.cmdWallet.get_icon_conf(command='sendtx', args=args) 143 | response = self.tx_command("sendtx", conf=conf, password='qwer1234%') 144 | self.assertEqual(response.get('error', False), False) 145 | # check result 146 | tx_hash = response['result'] 147 | conf = self.cmd.cmdWallet.get_icon_conf('txresult', {'hash': tx_hash}) 148 | transaction_result_response = self.cmd.cmdWallet.txresult(conf) 149 | self.assertFalse(transaction_result_response.get('error', False)) 150 | # check the stepLimit in the confirmed tx 151 | confirmed_transaction = self.cmd.cmdWallet.txbyhash(conf) 152 | self.assertTrue(confirmed_transaction['result']['stepLimit'], '0x3000000') 153 | 154 | # no stepLimit in the json file, invoke estimateStep 155 | send_json_path = os.path.join(TEST_UTIL_DIRECTORY, 'send_wo_steplimit.json') 156 | args = {"json_file": send_json_path} 157 | conf = self.cmd.cmdWallet.get_icon_conf(command='sendtx', args=args) 158 | response = self.tx_command("sendtx", conf=conf) 159 | self.assertEqual(response.get('error', False), False) 160 | # check result 161 | tx_hash = response['result'] 162 | conf = self.cmd.cmdWallet.get_icon_conf('txresult', {'hash': tx_hash}) 163 | transaction_result_response = self.cmd.cmdWallet.txresult(conf) 164 | self.assertFalse(transaction_result_response.get('error', False)) 165 | 166 | # no stepLimit, with keystore 167 | send_json_path = os.path.join(TEST_UTIL_DIRECTORY, 'send_wo_steplimit.json') 168 | args = {"json_file": send_json_path, 169 | "keyStore": os.path.join(TEST_UTIL_DIRECTORY, 'test_keystore')} 170 | conf = self.cmd.cmdWallet.get_icon_conf(command='sendtx', args=args) 171 | response = self.tx_command("sendtx", conf=conf, password='qwer1234%') 172 | self.assertEqual(response.get('error', False), False) 173 | # check result 174 | tx_hash = response['result'] 175 | conf = self.cmd.cmdWallet.get_icon_conf('txresult', {'hash': tx_hash}) 176 | transaction_result_response = self.cmd.cmdWallet.txresult(conf) 177 | self.assertFalse(transaction_result_response.get('error', False)) 178 | 179 | # use the stepLimit specified in the command line argument 180 | args = {"stepLimit": "0x1", "json_file": send_json_path} 181 | conf = self.cmd.cmdScore.get_icon_conf(command='sendtx', args=args) 182 | response = self.tx_command("sendtx", conf=conf) 183 | self.assertIsInstance(response.get('error', False), dict) 184 | 185 | # use the stepLimit in the config file 186 | tbears_cli_config_step_set_path = os.path.join(TEST_UTIL_DIRECTORY, 'test_tbears_cli_config_step_set.json') 187 | args = {"config": tbears_cli_config_step_set_path, "json_file": send_json_path} 188 | conf = self.cmd.cmdScore.get_icon_conf(command='sendtx', args=args) 189 | response = self.tx_command("sendtx", conf=conf) 190 | self.assertIsInstance(response.get('error', False), dict) --------------------------------------------------------------------------------