├── .travis.yml ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.rst ├── docs ├── Makefile ├── conf.py ├── index.rst ├── make.bat ├── src.rst └── usage.rst ├── pytest.ini ├── requirements.txt ├── requirements_dev.txt ├── setup.cfg ├── setup.py ├── src └── cita │ ├── __init__.py │ ├── blockchain_pb2.py │ ├── make_tx.py │ ├── sdk.py │ └── util.py └── tests ├── DoubleStorage.sol ├── Dummy.sol ├── SimpleStorage.sol └── test_sdk.py /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | services: 4 | - docker 5 | 6 | python: 7 | - "3.7" 8 | 9 | before_install: 10 | - docker pull ethereum/solc:0.4.24 11 | - docker run -d --name test_cita -p 1337:1337 cita/cita-ce:20.2.0-secp256k1-sha3 12 | /bin/bash -c 'cd /opt/cita/bin && 13 | ./cita create --super_admin "0x141d051b1b1922bf686f5df8aad45cefbcb0b696" --nodes "127.0.0.1:4000" && 14 | ./cita setup test-chain/0 && 15 | ./cita start test-chain/0 && 16 | sleep 1000' 17 | - pip install -r requirements_dev.txt 18 | 19 | script: 20 | - make sol 21 | - make test 22 | 23 | after_success: 24 | - coveralls 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include CONTRIBUTING.rst 2 | include HISTORY.rst 3 | include LICENSE 4 | include README.rst 5 | 6 | recursive-include tests * 7 | recursive-exclude * __pycache__ 8 | recursive-exclude * *.py[co] 9 | 10 | recursive-include docs *.rst conf.py Makefile make.bat *.jpg *.png *.gif 11 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TESTS ?= tests # 测试的目标, 默认是./tests目录 2 | SOL_INPUTS := $(wildcard tests/*.sol) 3 | SOL_OUTPUTS := $(patsubst %.sol,%.bin,$(SOL_INPUTS)) 4 | 5 | .PHONY: doc clean test only sol 6 | 7 | clean: 8 | rm -rf docs/_build dist/ tests/*.bin 9 | 10 | %.bin: %.sol 11 | cat $^ | sudo docker run -i --rm ethereum/solc:0.4.24 --bin --abi --optimize - > $@ 12 | 13 | sol: $(SOL_OUTPUTS) # 编译.sol文件 14 | 15 | doc: 16 | cd docs && $(MAKE) html 17 | cd docs/_build/html && tar -zcvf ../docs.tar.gz . &> /dev/null 18 | 19 | test: $(SOL_OUTPUTS) # 运行测试 20 | PYTHONPATH=$(PWD)/src pytest --cov=src -vv $(TESTS) 21 | 22 | only: $(SOL_OUTPUTS) # 只运行打了 @pytest.mark.only 注解的测试 23 | PYTHONPATH=$(PWD)/src pytest -m only --cov=src -vv $(TESTS) 24 | 25 | dist: setup.py setup.cfg MANIFEST.in ## builds source and wheel package 26 | PYTHONPATH=$(PWD)/src python setup.py sdist 27 | PYTHONPATH=$(PWD)/src python setup.py bdist_wheel 28 | ls -l dist 29 | 30 | # PROJ := cita 31 | # upload_doc: doc 32 | # PYTHONPATH=$(PWD)/src python docs/upload_doc.py $(PROJ) SERVICE_API $(PROJ) docs/_build/docs.tar.gz 33 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | |Python| |Travis| |Coverage| |License| 2 | 3 | 4 | CITA Python SDK 5 | ------------------- 6 | 7 | ``cita-sdk-python`` 是 `CITA高性能区块链 `_ 的Python binding, 提供下述功能: 8 | 9 | - ``CitaClient`` 类封装了CITA JSON RPC的调用. 10 | - ``ContractClass`` 类封装了合约的ABI. 使用Python的语法来完成合约部署. 11 | - ``ContractProxy`` 类封装了已部署的合约. 使得对合约方法的调用看起来就像对Python Object方法的调用. 12 | 13 | 14 | 安装 15 | ---------- 16 | 17 | ``cita-sdk-python`` 需要 Python 3.7 或以上版本的Python来运行:: 18 | 19 | $ PYTHONPATH=$PWD/src python setup.py install 20 | 21 | 22 | 打包 23 | ---------- 24 | 25 | :: 26 | 27 | $ make dist 28 | $ ls dist/ 29 | cita_sdk_python-0.1.0-py37-none-any.whl cita_sdk_python-0.1.0.tar.gz 30 | 31 | 32 | 文档 33 | ---------- 34 | 35 | :: 36 | 37 | $ make doc 38 | $ ls docs/_build/html 39 | genindex.html index.html _modules objects.inv py-modindex.html search.html searchindex.js _sources src.html _static usage.html 40 | 41 | 启动浏览器打开 ``index.html`` 即可查看详细文档. 42 | 43 | 44 | 常见用法 45 | ---------- 46 | 47 | 使用 ``CitaClient`` 执行 JSON RPC:: 48 | 49 | >>> from cita import CitaClient 50 | >>> client = CitaClient('http://127.0.0.1:1337', timeout=10) 51 | >>> client.get_peers() 52 | { 53 | 'amount': 3, 54 | 'peers': { 55 | '0x2aaeacf658e49f58973b4ef6f37a5c574a28822c': '127.0.0.1', 56 | '0x3ea53608732da3761ef41805da73f0d45d3e8e09': '127.0.0.1', 57 | '0x01cb0a8012b75ea156eaef3e827547f760dd917a': '127.0.0.1' 58 | }, 59 | 'errorMessage': None 60 | } 61 | 62 | >>> client.get_latest_block_number() 63 | 733539 64 | 65 | >>> client.create_key() 66 | { 67 | 'private': '0xc2c9d4828cd0755542d0dfc9deaf44f2f40bb13d35f5907a50f60d8ccabf9832', 68 | 'public': '0xce5bd22370bd45c17210babfb0d357c0b6ff74e9fd66fa120c795d849feaa49b115d49f82ffa27854c884fed25feee0bafc3833847abafaddb423a16af301b2c', 69 | 'address': '0xc9ee0f9193796ffbbed9cd6d63ed4e1483b1eafc' 70 | } 71 | 72 | 73 | 假设下述合约保存在当前目录下的 ``SimpleStorage.sol`` 文件中:: 74 | 75 | contract SimpleStorage { 76 | uint x; 77 | 78 | constructor(uint _x) public { 79 | x = _x; 80 | } 81 | 82 | function set(uint _x) public { 83 | x = _x; 84 | } // 此方法的hash码为: 0x60fe47b1 85 | 86 | function get() public constant returns (uint) { 87 | return x; 88 | } // 此方法的hash码为: 0x6d4ce63c 89 | } 90 | 91 | 92 | 使用 ``solc`` 编译合约, 将结果保存在同一路径下的 ``SimpleStorage.bin`` 文件中:: 93 | 94 | $ cat SimpleStorage.sol | sudo docker run -i --rm ethereum/solc:0.4.24 --bin --abi --optimize - > SimpleStorage.bin 95 | 96 | 97 | 使用 ``ContractClass`` 加载 ``SimpleStorage.bin`` 中的内容, 部署并执行合约:: 98 | 99 | >>> from cita import CitaClient, ContractClass 100 | >>> client = CitaClient('http://127.0.0.1:1337') 101 | >>> private_key = client.create_key()['private'] 102 | 103 | # 初始化ContractClass 104 | >>> simple_class = ContractClass(Path('tests/SimpleStorage.sol'), client) 105 | 106 | # 部署一个合约实例 107 | >>> init_x = 100 108 | >>> simple_obj, contract_addr, tx_hash = simple_class.instantiate(private_key, init_x) 109 | 110 | # 调用Immutable合约方法 111 | >>> simple_obj.get() 112 | 100 113 | 114 | # 调用Mutable合约方法 115 | >>> tx_hash = simple_obj.set(200) 116 | 117 | # 等待交易确认 118 | >>> client.confirm_transaction(tx_hash) 119 | 120 | # 再次调用Immutable合约方法 121 | >>> simple_obj.get() 122 | 200 123 | 124 | 125 | .. |Python| image:: https://img.shields.io/badge/Python-3.7-blue?logo=python&logoColor=white 126 | :alt: PyPI - Python Versions 127 | 128 | .. |Travis| image:: https://travis-ci.com/citahub/cita-sdk-python.svg?branch=master 129 | :target: https://travis-ci.com/citahub/cita-sdk-python 130 | 131 | .. |Coverage| image:: https://coveralls.io/repos/github/citahub/cita-sdk-python/badge.svg?branch=master 132 | :target: https://coveralls.io/github/citahub/cita-sdk-python?branch=master 133 | 134 | .. |License| image:: https://img.shields.io/badge/License-Apache%202.0-blue.svg 135 | :target: https://opensource.org/licenses/Apache-2.0 136 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 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) 21 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | import sys 14 | import os 15 | sys.path.insert(0, os.path.abspath('../src')) 16 | from datetime import date 17 | import importlib 18 | 19 | # -- Project information ----------------------------------------------------- 20 | 21 | project = 'cita-sdk-python' 22 | mod = importlib.import_module('cita') 23 | 24 | author = mod.__author__ 25 | copyright = f'{date.today().year}, {author}.' 26 | 27 | # The version info for the project you're documenting, acts as replacement 28 | # for |version| and |release|, also used in various other places throughout 29 | # the built documents. 30 | # 31 | # The short X.Y version. 32 | version = mod.__version__ 33 | # The full version, including alpha/beta/rc tags. 34 | release = mod.__version__ 35 | 36 | # -- General configuration --------------------------------------------------- 37 | 38 | # Add any Sphinx extension module names here, as strings. They can be 39 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 40 | # ones. 41 | extensions = [ 42 | 'sphinx.ext.autodoc', 43 | 'sphinx_autodoc_typehints', 44 | 'sphinx.ext.viewcode', 45 | 'sphinx.ext.doctest', 46 | 'sphinx.ext.todo', 47 | 'sphinx.ext.autosectionlabel', 48 | ] 49 | 50 | # Add any paths that contain templates here, relative to this directory. 51 | templates_path = ['_templates'] 52 | 53 | # The language for content autogenerated by Sphinx. Refer to documentation 54 | # for a list of supported languages. 55 | # 56 | # This is also used if you do content translation via gettext catalogs. 57 | # Usually you set "language" from the command line for these cases. 58 | language = 'en' 59 | 60 | # List of patterns, relative to source directory, that match files and 61 | # directories to ignore when looking for source files. 62 | # This pattern also affects html_static_path and html_extra_path. 63 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 64 | 65 | # The name of the Pygments (syntax highlighting) style to use. 66 | pygments_style = 'sphinx' 67 | 68 | smartquotes = False 69 | 70 | # -- Options for HTML output ------------------------------------------------- 71 | 72 | # The theme to use for HTML and HTML Help pages. See the documentation for 73 | # a list of builtin themes. 74 | # 75 | html_theme = 'alabaster' 76 | 77 | # Add any paths that contain custom static files (such as style sheets) here, 78 | # relative to this directory. They are copied after the builtin static files, 79 | # so a file named "default.css" will overwrite the builtin "default.css". 80 | html_static_path = ['_static'] 81 | 82 | # Theme options are theme-specific and customize the look and feel of a 83 | # theme further. For a list of options available for each theme, see the 84 | # documentation. 85 | # 86 | 87 | _font_family = '-apple-system,BlinkMacSystemFont,Helvetica Neue,PingFang SC,Microsoft YaHei Mono,Source Han Sans SC,Noto Sans CJK SC,WenQuanYi Micro Hei,sans-serif;' 88 | html_theme_options = { 89 | 'description': mod.__description__, 90 | 'body_max_width': 'auto', 91 | 'page_width': '1200px', 92 | 'sidebar_width': '300px', 93 | 'caption_font_family': _font_family, 94 | 'font_family': _font_family, 95 | 'head_font_family': _font_family, 96 | } 97 | 98 | # -- Extension configuration ------------------------------------------------- 99 | autodoc_default_options = { 100 | # 'members': 'var1, var2', 101 | # 'member-order': 'bysource', 102 | 'special-members': '__init__', 103 | # 'undoc-members': True, 104 | # 'exclude-members': '__weakref__' 105 | } 106 | 107 | # -- Options for todo extension ---------------------------------------------- 108 | 109 | # If true, `todo` and `todoList` produce output, else they produce nothing. 110 | todo_include_todos = True 111 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | cita-sdk-python 文档 2 | =============================== 3 | 4 | .. toctree:: 5 | :hidden: 6 | :maxdepth: 3 7 | 8 | 首页 9 | 基本用法 10 | References 11 | 12 | ``cita-sdk-python`` 是 `CITA高性能区块链 `_ 的Python binding, 提供下述功能: 13 | 14 | - :class:`~cita.CitaClient` 类封装了CITA JSON RPC的调用. 15 | - :class:`~cita.ContractClass` 类封装了合约文件和ABI. 使用Python的语法来完成合约部署. 16 | - :class:`~cita.ContractProxy` 类封装了已部署的合约. 使得对合约方法的调用看起来就像对Python Object方法的调用. 17 | 18 | 请先阅读 :ref:`基本用法`, 更多细节可以查阅 :ref:`References` . 19 | 20 | Index 21 | ===== 22 | 23 | * :ref:`genindex` 24 | * :ref:`modindex` 25 | * :ref:`search` 26 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/src.rst: -------------------------------------------------------------------------------- 1 | References 2 | =========== 3 | 4 | 常量定义 5 | ---------- 6 | 7 | - ``DEFAULT_QUOTA = 10_000_000`` 默认调用每个合约方法所消耗的quota上限. 8 | 9 | 10 | CitaClient 11 | ----------- 12 | 13 | .. autoclass:: cita.CitaClient 14 | :members: 15 | :undoc-members: 16 | :show-inheritance: 17 | 18 | 19 | ContractClass 20 | -------------- 21 | 22 | .. autoclass:: cita.ContractClass 23 | :members: 24 | :undoc-members: 25 | :show-inheritance: 26 | 27 | 28 | ContractProxy 29 | -------------- 30 | 31 | .. autoclass:: cita.ContractProxy 32 | :members: 33 | :undoc-members: 34 | :show-inheritance: 35 | 36 | 37 | 辅助功能 38 | ----------------------- 39 | 40 | .. automodule:: cita 41 | :members: join_param, equal_param, encode_param, decode_param, param_to_bytes, param_to_str 42 | :undoc-members: 43 | :show-inheritance: 44 | 45 | .. autoclass:: cita.sdk.Functor 46 | :members: 47 | :undoc-members: 48 | :show-inheritance: 49 | 50 | .. autoclass:: cita.sdk.ABI 51 | :members: 52 | :undoc-members: 53 | :show-inheritance: 54 | -------------------------------------------------------------------------------- /docs/usage.rst: -------------------------------------------------------------------------------- 1 | 基本用法 2 | =========== 3 | 4 | 文档中展示了 ``cita-sdk-python`` 的常见用法. 示例中的调用方式往往使用了默认参数, 所以建议点击方法名称, 跳转至Reference, 从而了解详细用法. 另外, 由于区块链的配置或内部状态不同, 所以每次执行示例可能会得到不同的结果. 5 | 6 | 7 | 使用CitaClient 8 | ---------------- 9 | 10 | 初始化 11 | ~~~~~~~~~ 12 | 13 | :class:`~cita.CitaClient` 对 CITA JSON RPC 做了封装. 通常情况下, 初始化只需要提供 JSON RPC 服务的url即可, 比如: ``http://127.0.0.1:1337`` . 如果链的版本号 和 ``chain_id`` 不是默认值, 可以在构造时手工指定. 之后就可以进行方法调用了:: 14 | 15 | >>> from cita import CitaClient 16 | >>> client = CitaClient('http://127.0.0.1:1337', timeout=10) 17 | 18 | 如果操作会发起 RPC 调用且后端未在 ``timeout`` 时间内返回, 则会抛出超时异常. 建议同时参考CITA官方文档中的 `JSON-RPC 列表 `_ 部分. 19 | 20 | 21 | .. note:: 22 | 23 | 目前仅支持 ``secp256k1`` 的加密方案. 24 | 25 | 26 | 节点信息 27 | ~~~~~~~~~~~~~~~~~ 28 | 29 | - :meth:`~cita.CitaClient.get_peer_count` 可以获得CITA 后端的兄弟节点数量. 30 | - :meth:`~cita.CitaClient.get_peers` 可以获取节点的详细信息. 31 | 32 | :: 33 | 34 | >>> from cita import CitaClient 35 | >>> client = CitaClient('http://127.0.0.1:1337') 36 | >>> client.get_peer_count() 37 | 2 38 | >>> client.get_peers() 39 | { 40 | 'amount': 3, 41 | 'peers': { 42 | '0x2aaeacf658e49f58973b4ef6f37a5c574a28822c': '127.0.0.1', 43 | '0x3ea53608732da3761ef41805da73f0d45d3e8e09': '127.0.0.1', 44 | '0x01cb0a8012b75ea156eaef3e827547f760dd917a': '127.0.0.1' 45 | }, 46 | 'errorMessage': None 47 | } 48 | 49 | 50 | 元数据 51 | ~~~~~~~~~~~~~~~~~~~~ 52 | 53 | :meth:`~cita.CitaClient.get_meta_data` 获取链上元数据:: 54 | 55 | >>> from cita import CitaClient 56 | >>> client = CitaClient('http://127.0.0.1:1337') 57 | >>> client.get_meta_data() 58 | { 59 | 'chainId': 0, 60 | 'chainIdV1': '0x1', 61 | 'chainName': 'CITA-TEST', 62 | 'operator': 'test-operator', 63 | 'website': 'http://localhost', 64 | 'genesisTimestamp': 1587639835490, 65 | 'validators': ['0x1ea2fb0843953ecb15a79f9751ed963e0dc8720f'], 66 | 'blockInterval': 3000, 67 | 'tokenName': 'FJ', 68 | 'tokenSymbol': 'FJ', 69 | 'tokenAvatar': 'https://cdn.citahub.com/icon_cita.png', 70 | 'version': 2, 71 | 'economicalModel': 0 72 | } 73 | 74 | 区块信息 75 | ~~~~~~~~~~~~~~~ 76 | 77 | - :meth:`~cita.CitaClient.get_latest_block_number` 获取当前区块高度. 78 | - :meth:`~cita.CitaClient.get_block_by_hash` 可以根据交易hash查询区块详情. 79 | - :meth:`~cita.CitaClient.get_block_by_number` 可以根据区块编号查询区块详情. 80 | 81 | :: 82 | 83 | >>> from cita import CitaClient 84 | >>> client = CitaClient('http://127.0.0.1:1337') 85 | >>> client.get_latest_block_number() 86 | 733539 87 | >>> r1 = client.get_block_by_number(135342) 88 | >>> r2 = client.get_block_by_hash('0x5d28d1ceea302de6a935ee1ed8aff34aecd2c29543a75744301e581c2be366b3') 89 | >>> assert r1 == r2 90 | >>> r1 91 | { 92 | 'version': 2, 93 | 'hash': '0x5d28d1ceea302de6a935ee1ed8aff34aecd2c29543a75744301e581c2be366b3', 94 | 'header': { 95 | 'timestamp': 1588048233794, 96 | 'prevHash': '0x338cb4b7c4e39c43cfa798884c7718cfb088babdc82e65aa2242c9d3aba0a398', 97 | 'number': '0x210ae', 98 | 'stateRoot': '0xf14d7af965ad2cd1ed1576bfb3dd3f64fae57d8652c13e6865516d9c83a1b856', 99 | 'transactionsRoot': '0x2468067d5af66594ec070891edde6e45ae10dffb47f0d81467302a893a92bdac', 100 | 'receiptsRoot': '0x81a7a530eaeae617bf617b4515077a033d8a6b3a0aaea9822491d1f1ce4e591a', 101 | 'quotaUsed': '0x11822', 102 | 'proof': { 103 | 'Bft': { 104 | 'proposal': '0xc093d0fb3e31a0e0f182dd8058b3f822fce927bcae03fffbde478314914413bb', 105 | 'height': 135341, 106 | 'round': 0, 107 | 'commits': { 108 | '0x1ea2fb0843953ecb15a79f9751ed963e0dc8720f': '0xeb7fe339067e8f33ca46ca4dbc5dc3527798ad8911d121156aa65f392dafb6b65b02a82a56a2a2d85a93b6d50e29e2c5e96e488f757468da4e6397f86a1343c301' 109 | } 110 | } 111 | }, 112 | 'proposer': '0x1ea2fb0843953ecb15a79f9751ed963e0dc8720f' 113 | }, 114 | 'body': { 115 | 'transactions': ['0x2468067d5af66594ec070891edde6e45ae10dffb47f0d81467302a893a92bdac'] 116 | } 117 | } 118 | 119 | 120 | 交易信息 121 | ~~~~~~~~~~~~ 122 | 123 | - :meth:`~cita.CitaClient.get_transaction_count` 统计由指定账户发起的交易数量. 124 | - :meth:`~cita.CitaClient.get_transaction` 获取交易详情. 125 | - :meth:`~cita.CitaClient.decode_transaction_content` 解码交易内容. 126 | 127 | :: 128 | 129 | >>> from cita import CitaClient 130 | >>> client = CitaClient('http://127.0.0.1:1337') 131 | >>> client.get_transaction_count('0x0c6e2197844c7bff3a87f60ce931746f17572a00') 132 | 1144 133 | >>> r = client.get_transaction('0x2468067d5af66594ec070891edde6e45ae10dffb47f0d81467302a893a92bdac') 134 | >>> r 135 | { 136 | 'hash': '0x2468067d5af66594ec070891edde6e45ae10dffb47f0d81467302a893a92bdac', 137 | 'content': '0x0a950612064f5143374d371880808080042084a2082aa40582cc33270000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000025c38f11b7ad49c437d69851136c202d505aab4999500000244017c4f38000000000000000000000000297cb9765bb8abb603740b352ed549cf656a295262343766313164326162633234383236613466653735363233383466336436310000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000012b00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001727b22757365725f61646472223a22307832393763623937363562623861626236303337343062333532656435343963663635366132393532222c226576656e745f6e616d65223a2271696e67636875616e67706b5f335f33222c22616374696f6e5f74696d65223a22323032302d30342d32382031323a33303a32382e353936323438222c22616374696f6e5f74797065223a22766f7465222c22616374696f6e5f706172616d223a7b22746172676574223a226234376631316432616263323438323661346665373536323338346633643631222c227469636b6574223a312c22636f6e74726962223a312c227a68756c695f726577617264223a66616c73652c2272656d61696e5f7469636b6574223a3239397d2c22616374696f6e5f64657363223a225c75363239355c75373936382c205c75386432315c75373332652b312c205c75376432665c75386261315c75386432315c753733326520312c205c75346635395c753739363820323939227d0000000000000000000000000000000000003220000000000000000000000000000000000000000000000000000000000000000040024a14ffffffffffffffffffffffffffffffffff02000e5220000000000000000000000000000000000000000000000000000000000000000112415e1c0796e7bed6f7b5d29f661bec846f6b57328de27cfb4b3919315cc64268237bdfb64c7b317ebf58451bcd7a7d9ad1f0a4c3055db2e1a6526b8e60b242e2a700', 138 | 'from': '0x0c6e2197844c7bff3a87f60ce931746f17572a00', 139 | 'blockNumber': '0x210ae', 140 | 'blockHash': '0x5d28d1ceea302de6a935ee1ed8aff34aecd2c29543a75744301e581c2be366b3', 141 | 'index': '0x0' 142 | } 143 | >>> client.decode_transaction_content(r['content']) 144 | { 145 | 'transaction': { 146 | 'chaid_id': 0, 147 | 'chain_id_v1': '0x0000000000000000000000000000000000000000000000000000000000000001', 148 | 'data': '0x82cc33270000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000025c38f11b7ad49c437d69851136c202d505aab4999500000244017c4f38000000000000000000000000297cb9765bb8abb603740b352ed549cf656a295262343766313164326162633234383236613466653735363233383466336436310000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000012b00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001727b22757365725f61646472223a22307832393763623937363562623861626236303337343062333532656435343963663635366132393532222c226576656e745f6e616d65223a2271696e67636875616e67706b5f335f33222c22616374696f6e5f74696d65223a22323032302d30342d32382031323a33303a32382e353936323438222c22616374696f6e5f74797065223a22766f7465222c22616374696f6e5f706172616d223a7b22746172676574223a226234376631316432616263323438323661346665373536323338346633643631222c227469636b6574223a312c22636f6e74726962223a312c227a68756c695f726577617264223a66616c73652c2272656d61696e5f7469636b6574223a3239397d2c22616374696f6e5f64657363223a225c75363239355c75373936382c205c75386432315c75373332652b312c205c75376432665c75386261315c75386432315c753733326520312c205c75346635395c753739363820323939227d000000000000000000000000000000000000', 149 | 'nonce': 'OQC7M7', 150 | 'quota': 1073741824, 151 | 'to': '', 152 | 'to_v1': '0xffffffffffffffffffffffffffffffffff02000e', 153 | 'valid_until_block': 135428, 154 | 'value': '0x0000000000000000000000000000000000000000000000000000000000000000', 155 | 'version': 2 156 | }, 157 | 'signature': '0x5e1c0796e7bed6f7b5d29f661bec846f6b57328de27cfb4b3919315cc64268237bdfb64c7b317ebf58451bcd7a7d9ad1f0a4c3055db2e1a6526b8e60b242e2a700', 158 | 'crypto': 0 159 | } 160 | 161 | 162 | 创建账户 163 | ~~~~~~~~~~~ 164 | 165 | :meth:`~cita.CitaClient.create_key` 可以创建账户, 返回账户地址, 公钥, 私钥. 用于交易签名或合约调用:: 166 | 167 | >>> from cita import CitaClient 168 | >>> client = CitaClient('http://127.0.0.1:1337') 169 | >>> client.create_key() 170 | { 171 | 'private': '0xc2c9d4828cd0755542d0dfc9deaf44f2f40bb13d35f5907a50f60d8ccabf9832', 172 | 'public': '0xce5bd22370bd45c17210babfb0d357c0b6ff74e9fd66fa120c795d849feaa49b115d49f82ffa27854c884fed25feee0bafc3833847abafaddb423a16af301b2c', 173 | 'address': '0xc9ee0f9193796ffbbed9cd6d63ed4e1483b1eafc' 174 | } 175 | 176 | 177 | 发起并确认交易 178 | ~~~~~~~~~~~~~~ 179 | 180 | - :meth:`~cita.CitaClient.send_raw_transaction` 直接对应JSON RPC的 ``sendRawTransaction`` 方法. 输入参数是签名后的数据. 181 | - :meth:`~cita.CitaClient.send_transaction` 比前者更进一步, 封装了签名算法. 182 | - :meth:`~cita.CitaClient.get_transaction_receipt` 获取交易回执. 如果在指定时间内获得回执, 说明交易已经在一个节点执行完毕. 183 | - :meth:`~cita.CitaClient.confirm_transaction` 等待交易在全部节点达成共识后再返回交易回执. 184 | 185 | :: 186 | 187 | >>> from cita import CitaClient 188 | >>> client = CitaClient('http://127.0.0.1:1337') 189 | >>> private_key = '0x...' 190 | >>> to_addr = '0x...' 191 | >>> code = '0x...' 192 | >>> tx_hash = client.send_transaction(private_key, to_addr, code) 193 | >>> tx_hash 194 | '0x2468067d5af66594ec070891edde6e45ae10dffb47f0d81467302a893a92bdac' 195 | >>> r1 = client.get_transaction_receipt(tx_hash, timeout=10) 196 | >>> r2 = client.confirm_transaction(tx_hash, timeout=10) 197 | >>> assert r1 == r2 198 | >>> r1 199 | { 200 | 'transactionHash': '0x2468067d5af66594ec070891edde6e45ae10dffb47f0d81467302a893a92bdac', 201 | 'transactionIndex': '0x0', 202 | 'blockHash': '0x5d28d1ceea302de6a935ee1ed8aff34aecd2c29543a75744301e581c2be366b3', 203 | 'blockNumber': '0x210ae', 204 | 'cumulativeQuotaUsed': '0x11822', 205 | 'quotaUsed': '0x11822', 206 | 'contractAddress': None, 207 | 'logs': [], 208 | 'root': None, 209 | 'logsBloom': '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', 210 | 'errorMessage': None 211 | } 212 | 213 | 214 | 部署合约 215 | ~~~~~~~~~~~ 216 | 217 | 本质上就是一个交易. 所以也可以使用 :meth:`~cita.CitaClient.send_raw_transaction` 和 :meth:`~cita.CitaClient.send_transaction` 方法完成. 但是使用 :meth:`~cita.CitaClient.deploy_contract` 可以更明确一些. 所需参数是私钥, 合约的bytecode, 合约初始化参数:: 218 | 219 | >>> from cita import CitaClient 220 | >>> client = CitaClient('http://127.0.0.1:1337') 221 | >>> private_key = '0x...' 222 | >>> bytecode = '0x...' 223 | >>> param = '0x...' 224 | >>> tx_hash = client.deploy_contract(private_key, bytecode, param) 225 | >>> tx_hash 226 | '0x85077e49b10e57bd93f107570ced9c7046157d891a1873c3339a270246800c90' 227 | >>> client.get_transaction_receipt(tx_hash) 228 | { 229 | 'transactionHash': '0x85077e49b10e57bd93f107570ced9c7046157d891a1873c3339a270246800c90', 230 | 'transactionIndex': '0x0', 231 | 'blockHash': '0xf0dde2aa7b427b9bbb11e03c2adfced395463ba095d9ce7487eee0c1026ecb6a', 232 | 'blockNumber': '0x22261', 233 | 'cumulativeQuotaUsed': '0x2fa8d', 234 | 'quotaUsed': '0x2fa8d', 235 | 'contractAddress': '0xe74a75fa682664dcee4b41f34e76bcfbbb45cdd6', 236 | 'logs': [], 237 | 'root': None, 238 | 'logsBloom': '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', 239 | 'errorMessage': None 240 | } 241 | 242 | 交易回执中的 ``contractAddress`` 字段表明了合约的链上地址. 合约参数需要进行编码, 把Python中的数值转化为区块链可以理解的格式. 这部分在 :ref:`编码解码` 中详述. 243 | 244 | 245 | 合约的bytecode和ABI 246 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 247 | 248 | 合约文件经过编译后形成bytecode, 从而定义其链上行为. 为了让其他用户了解合约的使用方法, 还可以指定ABI. 249 | 250 | - :meth:`~cita.CitaClient.store_abi` 可以对给定地址的合约添加ABI. 251 | - :meth:`~cita.CitaClient.get_abi` 可以读取给定地址的合约的ABI. 252 | - :meth:`~cita.CitaClient.get_code` 可以读取给定地址的合约的ABI. 253 | 254 | 255 | :: 256 | 257 | >>> from cita import CitaClient 258 | >>> client = CitaClient('http://127.0.0.1:1337') 259 | 260 | # 部署合约 261 | >>> private_key = '0x...' 262 | >>> bytecode = '0x...' 263 | >>> param = '0x...' 264 | >>> tx_hash = client.deploy_contract(private_key, bytecode, param) 265 | >>> contract_addr = client.confirm_transaction(tx_hash)['contractAddress'] 266 | 267 | # 获取合约的字节码 268 | >>> assert client.get_code(contract_addr) == bytecode 269 | 270 | # 给合约添加ABI 271 | >>> abi = [{ 272 | "constant": true, 273 | "inputs": [], 274 | "name": "get", 275 | "outputs": [{ 276 | "name": "", 277 | "type": "uint256" 278 | }], 279 | "payable": false, 280 | "stateMutability": "view", 281 | "type": "function" 282 | }, { 283 | "inputs": [{ 284 | "name": "_x", 285 | "type": "uint256" 286 | }], 287 | "payable": false, 288 | "stateMutability": "nonpayable", 289 | "type": "constructor" 290 | }] 291 | >>> tx_hash = client.store_abi(private_key, contract_addr, json.dumps(abi)) 292 | >>> client.confirm_transaction(tx_hash) 293 | 294 | # 读取合约的ABI 295 | >>> assert client.get_abi(contract_addr) == abi 296 | 297 | 298 | 299 | 调用合约 300 | ~~~~~~~~~~ 301 | 302 | 直接使用 :class:`~cita.CitaClient` 进行合约的部署或方法调用是比较繁琐的, 因为需要手工处理参数和返回值的编码解码问题, 手工处理方法hash等问题, 语法上也不够自然. 建议使用 :ref:`使用ContractClass` 来完成这些工作. 303 | 304 | 305 | 编码解码 306 | ^^^^^^^^^^^^^^ 307 | 308 | 编码解码需要使用四个辅助函数来完成. 309 | 310 | - 编码 :func:`~cita.encode_param` 311 | - 解码 :func:`~cita.decode_param` 312 | - 将数据转化为 ``0x...`` 的形式 :func:`~cita.param_to_str` 313 | - 将数据转化为 ``bytes`` 的形式 :func:`~cita.param_to_bytes` 314 | 315 | 316 | :: 317 | 318 | >>> from cita import encode_param, decode_param, param_to_str, param_to_bytes 319 | 320 | # 处理单一参数 321 | >>> r = encode_param('string', 'abc') 322 | >>> param_to_str(r) 323 | '0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000036162630000000000000000000000000000000000000000000000000000000000' 324 | >>> assert decode_param('string', r) == 'abc' 325 | 326 | # 处理多个参数 327 | >>> r = encode_param('(int,bool,bytes[])', (-1, True, [b'abc', b'def'])) 328 | >>> param_to_str(r) 329 | '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000003616263000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000036465660000000000000000000000000000000000000000000000000000000000' 330 | >>> assert decode_param('(int,bool,bytes[])', r) == (-1, True, (b'abc', b'def')) 331 | 332 | .. note:: 333 | 334 | - 类型字符串, 比如 ``(int,bool,bytes[])`` 中是不可以有空格的. 否则函数会报错. 335 | - 对于单一参数, 无需写成 ``(string)`` 元组形式. 解码后也是直接得到数值, 无需提取元组的元素. 也就是说类型字符串 ``(string)`` 和 ``string`` 是等价的. 336 | - 对于数组, 解码后会得到 ``tuple`` 而不是 ``list``. 一般情况下, 这个区别并不影响业务逻辑. 337 | 338 | 339 | 执行调用 340 | ^^^^^^^^^^^^ 341 | 342 | 合约方法有Immutable和Mutable的区别. 声明为 ``const``, ``view``, ``pure`` 的合约方法为Immutable方法, 其他为Mutable方法: 343 | 344 | - :meth:`~cita.CitaClient.call_readonly_func` 可以调用合约的Immutable方法. 345 | - 输入合约地址 346 | - 输入合约方法的hash码 347 | - 输入编码后的方法参数 348 | - 输出编码后的返回值 349 | 350 | - :meth:`~cita.CitaClient.call_func` 可以调用合约的Mutable方法. 351 | - 输入合约地址 352 | - 输入合约方法的hash码 353 | - 输入调用者私钥 354 | - 输入编码后的方法参数 355 | - 输出交易hash 356 | 357 | :: 358 | 359 | 假设合约中定义了一个Mutable方法和一个Immutable方法: 360 | 361 | contract SimpleStorage { 362 | uint x; 363 | 364 | constructor(uint _x) public { 365 | x = _x; 366 | } 367 | 368 | function set(uint _x) public { 369 | x = _x; 370 | } // 此方法的hash码为: 0x60fe47b1 371 | 372 | function get() public constant returns (uint) { 373 | return x; 374 | } // 此方法的hash码为: 0x6d4ce63c 375 | } 376 | 377 | >>> from cita import CitaClient 378 | >>> client = CitaClient('http://127.0.0.1:1337') 379 | >>> contract_addr = '0x...' # 合约部署地址 380 | >>> private_key = '0x...' 381 | >>> param = encode_param('uint', 200) 382 | # set x = 200 383 | >>> tx_hash = client.call_func(private_key, contract_addr, '0x60fe47b1', param=param) 384 | >>> client.confirm_transaction(tx_hash) 385 | # get x 386 | >>> result = client.call_readonly_func(contract_addr, '0x6d4ce63c') 387 | >>> decode_param(result) 388 | 200 389 | 390 | 391 | 设置调用模式 392 | ~~~~~~~~~~~~~~~ 393 | 394 | 在调用合约的只读方法时, 默认会使用已共识的区块信息, 即 ``latest`` . 但也可以设置为使用刚出块还未共识的区块信息, 即 ``pending`` . 这个模式适合于只有一个CITA节点或单元测试. 可以通过 :meth:`~cita.CitaClient.set_call_mode` 进行设置. 设置后会影响由此实例发起的之后所有的调用. 395 | 396 | >>> from cita import CitaClient, ContractClass 397 | >>> client = CitaClient('http://127.0.0.1:1337') 398 | >>> client.set_call_mode('pending') 399 | 400 | 401 | 使用ContractClass 402 | ----------------------------- 403 | 404 | :class:`~cita.ContractClass` 将 ``.sol`` 的合约文件封装为一个Python的对象工厂, 产品就是一个个部署上链的合约对象. 同时, :class:`~cita.ContractProxy` 对合约对象进行封装, 将合约方法的调用转化为Python对象方法的调用, 隐藏了方法名, 参数, 返回值的编码解码步骤, 从而大幅简化开发工作, 语句表达上也更加自然. 下面的例子使用的合约跟 :ref:`执行调用` 中的相同:: 405 | 406 | >>> from cita import CitaClient, ContractClass 407 | >>> client = CitaClient('http://127.0.0.1:1337') 408 | >>> private_key = client.create_key()['private'] 409 | 410 | # 构造 ContractClass, 输入合约文件所在的路径. 411 | >>> simple_class = ContractClass(Path('tests/SimpleStorage.sol'), client) 412 | >>> simple_class.name 413 | 'SimpleStorage' 414 | 415 | # 部署合约, 将 100 传递给合约构造函数. 返回 (ContractProxy对象, 合约地址, 交易hash) 三元组. 416 | >>> simple_obj, contract_addr, tx_hash = simple_class.instantiate(private_key, 100) 417 | >>> client.confirm_transaction(tx_hash) 418 | 419 | # 通过ContractProxy对象读取 x 的值 420 | >>> simple_obj.get() 421 | 100 422 | 423 | # 通过ContractProxy对象改变 x 的值 424 | >>> tx_hash = simple_obj.set(200) 425 | >>> client.confirm_transaction(tx_hash) 426 | 427 | # 通过ContractProxy对象读取 x 的值 428 | >>> simple_obj.get() 429 | 200 430 | 431 | 432 | sol文件与bin文件 433 | ~~~~~~~~~~~~~~~~~~ 434 | 435 | ``cita-sdk-python`` 并不能解析 ``.sol`` 文件的内容. 需要使用 ``solc`` 之类的编译器, 将其转化为字节码和ABI才行. 建议用户采用 ``Makefile`` 中的方法来将 ``.sol`` 文件编译成标准的 ``.bin`` 文件:: 436 | 437 | cat some.sol | sudo docker run -i --rm ethereum/solc:0.4.24 --bin --abi --optimize - > some.bin 438 | 439 | :class:`~cita.ContractClass` 构造函数的第一个参数是 ``.sol`` 文件的路径. 要求在同目录下有同名的 ``.bin`` 文件:: 440 | 441 | ======= :SimpleStorage ======= 442 | Binary: 443 | 6080604052348015...957f150029 444 | Contract JSON ABI 445 | [{"constant":false,"inputs"..."type":"event"}] 446 | 447 | 通过读取上述内容, :class:`~cita.ContractClass` 可以了解合约的bytecode, 以及每个方法的类型, 名字, 参数, 返回值. 以便后续将之适配成普通的 Python 对象. :meth:`~cita.ContractClass.get_code` 和 :meth:`~cita.ContractClass.get_raw_abi` 会返回上述信息. 448 | 449 | .. warning:: 450 | 451 | 由于 Python 不支持函数重载, 而合约可以, 所以无法直接模拟这个功能. 建议避免在合约中使用重载. 如果某些方法必须重载, 则只能使用其方法hash码来触发调用了. 其他未重载的方法不受影响. 452 | 453 | 454 | 更方便的部署合约 455 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 456 | 457 | 有三种部署方法, 分别对应不同的使用场景. 458 | 459 | - :meth:`~cita.ContractClass.instantiate_raw` 发起部署合约的交易, 立即返回交易hash, 不会等待合约部署完成. 属于Low Level操作, 不太常用. 460 | - :meth:`~cita.ContractClass.instantiate` 部署合约并返回一个绑定于合约地址的 ``ContractProxy`` 对象. 很常用. 461 | - :meth:`~cita.ContractClass.batch_instantiate` 部署一批合约, 返回一组 ``ContractProxy`` 对象. 462 | 463 | 对于接收 0, 1, 2 个参数的合约构造函数的调用示例:: 464 | 465 | >>> from cita import CitaClient, ContractClass 466 | >>> client = CitaClient('http://127.0.0.1:1337') 467 | >>> private_key = client.create_key()['private'] 468 | 469 | # 合约构造函数 constructor() 470 | >>> dummy_class = ContractClass(Path('tests/Dummy.sol'), client) 471 | # 合约构造函数 constructor(uint _x) 472 | >>> simple_class = ContractClass(Path('tests/SimpleStorage.sol'), client) 473 | # 合约构造函数 constructor(uint _x, uint _y) 474 | >>> double_class = ContractClass(Path('tests/DoubleStorage.sol'), client) 475 | 476 | # 部署合约 477 | >>> x, y = 1, 2 478 | >>> dummy_obj, contract_addr, tx_hash = dummy_class.instantiate(private_key) 479 | >>> simple_obj, contract_addr, tx_hash = simple_class.instantiate(private_key, x) 480 | >>> double_obj, contract_addr, tx_hash = double_class.instantiate(private_key, x, y) 481 | 482 | 483 | 绑定合约地址 484 | ~~~~~~~~~~~~~ 485 | 486 | 如果已知某个 ``.sol`` 对应的 ``ContractClass`` , 以及合约地址. 可以通过 :meth:`~cita.ContractClass.bind` 来生成 ``ContractProxy`` 对象:: 487 | 488 | >>> from cita import CitaClient, ContractClass 489 | >>> client = CitaClient('http://127.0.0.1:1337') 490 | >>> simple_class = ContractClass(Path('tests/SimpleStorage.sol'), client) 491 | >>> contract_addr = '0x...' 492 | >>> private_key = '0x...' 493 | >>> simple_obj = simple_class.bind(contract_addr, private_key) 494 | 495 | 496 | 使用ContractProxy 497 | ------------------------------ 498 | 499 | :class:`~cita.ContractProxy` 可以由 :class:`~cita.ContractClass` 的实例化方法或者绑定来生成. 一旦得到 ``ContractProxy`` 对象, 就可以像调用Python方法那样操作合约了. :ref:`执行调用` 中列出了不同种类的方法需要使用不同的代码来调用. ``ContractProxy`` 尽可能的统一了调用方式, 唯一的区别是: 500 | 501 | - 对于Mutable合约方法, 只能返回交易hash; 502 | - 对于Immutable合约方法, 则会返回解码后的返回值. 如果返回多个值, 解码后是 ``tuple`` 形式. 503 | 504 | 接收 0, 1, 2 个参数的合约方法的调用示例:: 505 | 506 | >>> from cita import CitaClient, ContractClass 507 | >>> client = CitaClient('http://127.0.0.1:1337') 508 | >>> private_key = client.create_key()['private'] 509 | >>> contract_addr = '0x...' 510 | 511 | >>> simple_class = ContractClass(Path('tests/SimpleStorage.sol'), client) 512 | >>> simple_obj = simple_class.bind(contract_addr, private_key) 513 | 514 | # function set(uint _x) public 515 | >>> tx_hash = simple_obj.set(200) 516 | >>> client.confirm_transaction(tx_hash) 517 | 518 | # function get() public view returns (uint) 519 | >>> assert simple_obj.get() == 200 520 | 521 | # function function reset() public 522 | >>> simple_obj.reset() 523 | 524 | # function add_by_vec(uint[] a, uint[] b) public returns (uint) 525 | >>> tx_hash = simple_obj.add_by_vec([1, 2], [3, 4]) 526 | >>> client.confirm_transaction(tx_hash) 527 | >>> expect = 1 * 3 + 2 * 4 528 | >>> assert simple_obj.get() == expect 529 | 530 | 531 | Quota 532 | ~~~~~~~ 533 | 534 | 每个Mutable合约方法的调用, 执行中都会消耗Quota, 如果计算比较复杂, Quota消耗殆尽, 交易就会失败回滚. ``ContractProxy`` 默认每次调用的Quota是 ``DEFAULT_QUOTA = 10_000_000`` . 可以更精细的控制每个方法的Quota:: 535 | 536 | >>> new_quota = 1 537 | # 设置 function set 的quota 538 | >>> simple_obj.set.quota = new_quota 539 | # 设置 function add_by_vec 的quota 540 | >>> simple_obj.add_by_vec.quota = new_quota 541 | # 由于设置的quota很低, 所以合约方法的调用应该会失败. 542 | >>> tx_hash = simple_obj.set(300) 543 | >>> client.get_transaction_receipt(tx_hash) 544 | RuntimeError: Not enough base quota. 545 | 546 | ``obj..quota`` 会持续生效, 下一次对 ``method_name`` 的调用依旧会使用指定的quota. 除此之外, 还可以在 :meth:`cita.ContractClass.__init__` 时通过 ``func_name2quota`` 参数, 一次性指定每个合约方法的quota. 547 | 548 | 549 | 重载方法 550 | ~~~~~~~~~~ 551 | 552 | ``ContractProxy`` 对象除了可以通过合约方法名触发调用, 还可以通过合约方法的hash码来触发调用. 从而支持合约方法的重载:: 553 | 554 | >>> from cita import CitaClient, ContractClass 555 | >>> client = CitaClient('http://127.0.0.1:1337') 556 | >>> contract_addr = '0x...' 557 | >>> private_key = '0x...' 558 | 559 | >>> simple_class = ContractClass(Path('tests/SimpleStorage.sol'), client) 560 | >>> simple_obj = simple_class.bind(contract_addr, private_key) 561 | >>> assert simple_obj.get() == simple_obj['0x6d4ce63c']() 562 | 563 | 564 | 批量交易 565 | ---------------- 566 | 567 | 实际应用中, 经常会遇到需要处理大量交易的情况. 通过把一批零散交易打包成一个大交易, 可以大大提升 CITA 的吞吐量. ``cita-sdk-python`` 也对此提供了支持. 568 | 569 | 570 | 合约的批量部署 571 | ~~~~~~~~~~~~~~~~~ 572 | 573 | 通过 :meth:`~cita.CitaClass.batch_instantiate` 可以部署同一合约的多个实例. 由于 CITA 本身并不支持批量部署, 所以实现上仍是通过循环逐一部署, 只是提供了一些便利. 合约构造函数所需的参数, 通过数组传入:: 574 | 575 | >>> from cita import CitaClient, ContractClass 576 | >>> client = CitaClient('http://127.0.0.1:1337') 577 | >>> private_key = '0x...' 578 | 579 | >>> dummy_class = ContractClass(Path('tests/Dummy.sol'), client) 580 | >>> simple_class = ContractClass(Path('tests/SimpleStorage.sol'), client) 581 | >>> double_class = ContractClass(Path('tests/DoubleStorage.sol'), client) 582 | 583 | # 构造函数 constructor() 不需要参数, 部署三个实例. 584 | >>> init_values = [(), (), ()] 585 | >>> for dummy_obj, addr, tx_hash in dummy_class.batch_instantiate(private_key, init_values): 586 | ... print('Contract_addr:', addr) 587 | 588 | # 构造函数 constructor(uint _x), 部署三个实例. 对单一参数, 使用tuple包裹与uint等价. 589 | >>> init_values = [100, 200, (300,)] 590 | >>> for simple_obj, addr, tx_hash in simple_class.batch_instantiate(private_key, init_values): 591 | ... print('Contract_addr:', addr) 592 | 593 | # 构造函数 constructor(uint _x, uint _y), 部署三个实例. 参数必须是tuple形式. 594 | >>> init_values = [(100, 200), (300, 400), (500, 600)] 595 | >>> for double_obj, addr, tx_hash in double_class.batch_instantiate(private_key, init_values): 596 | ... print('Contract_addr:', addr) 597 | 598 | 599 | 批量调用 600 | ~~~~~~~~~~~~~~~ 601 | 602 | 当合约部署完毕后, CITA 内置支持批量合约方法的调用. 其本质是将每个合约调用所需的数据一次性提交给一个特殊的合约, 由后者在合约内部循环逐个执行交易. 这样可以节约很多通信和共识的计算, 从而大大提高执行效率. 但如果一批交易中有一个失败, 则整体失败. 所以在使用时需要谨慎. 603 | 604 | .. note:: 605 | 606 | 只有Mutable的合约方法可以进行批量交易. 一批交易只能使用同一私钥进行签名. 607 | 608 | 609 | 首先需要计算出每个交易所需的编码后的数据, 可以通过 :meth:`~cita.ContractProxy.get_tx_code` 解决:: 610 | 611 | >>> from cita import CitaClient, ContractClass 612 | >>> client = CitaClient('http://127.0.0.1:1337') 613 | >>> contract_addr1 = '0x...' 614 | >>> contract_addr2 = '0x...' 615 | >>> private_key = '0x...' 616 | 617 | # 绑定两个SimpleStorage合约实例 618 | >>> simple_class = ContractClass(Path('tests/SimpleStorage.sol'), client) 619 | >>> simple_obj1 = simple_class.bind(contract_addr1, private_key) 620 | >>> simple_obj2 = simple_class.bind(contract_addr2, private_key) 621 | 622 | # 收集每个交易所需的数据 tx_code 623 | >>> tx_code_list = [] 624 | 625 | # function reset() public 626 | >>> tx_code = simple_obj1.get_tx_code('reset') # 使用0个参数 627 | >>> tx_code_list.append(tx_code) 628 | 629 | # function set(uint _x) public 630 | >>> tx_code = simple_obj1.get_tx_code('set', 1) # 使用1个参数 631 | >>> tx_code_list.append(tx_code) 632 | 633 | # function add_by_vec(uint[] a, uint[] b) public returns (uint) 634 | >>> tx_code = simple_obj2.get_tx_code('add_by_vec', ([1, 2], [3, 4])) # 使用2个参数 635 | >>> tx_code_list.append(tx_code) 636 | 637 | 之后通过 :meth:`~cita.CitaClient.batch_call_func` 发起批量调用:: 638 | 639 | >>> tx_hash = client.batch_call_func(private_key, tx_code_list) 640 | >>> client.confirm_transaction(tx_hash) 641 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | addopts = --strict-markers --capture=no -p no:nameko --cov-report term-missing:skip-covered 3 | markers = 4 | only: only run specific testcases for debugging 5 | 6 | filterwarnings = 7 | ignore::DeprecationWarning -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | #requests==2.22.0 2 | #eth-abi==2.1.0 3 | #pysha3==1.0.2 4 | #ecdsa==0.15 5 | #secp256k1==0.13.2 6 | #protobuf==3.11.3 7 | -------------------------------------------------------------------------------- /requirements_dev.txt: -------------------------------------------------------------------------------- 1 | pip 2 | wheel==0.33.6 3 | pytest==5.3.1 4 | pytest-cov==2.8.1 5 | pytest-sugar==0.9.2 6 | coveralls 7 | Sphinx==2.2.2 8 | watchdog==0.9.0 9 | mypy==0.750 10 | flake8==3.7.8 11 | 12 | requests==2.22.0 13 | eth-abi==2.1.0 14 | pysha3==1.0.2 15 | ecdsa==0.15 16 | secp256k1==0.13.2 17 | protobuf==3.11.3 18 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | exclude = docs 3 | 4 | [mypy] 5 | mypy_path = src 6 | 7 | [bdist_wheel] 8 | python-tag = py37 9 | 10 | [aliases] 11 | # Define setup.py command aliases here 12 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """The setup script.""" 4 | import importlib 5 | from setuptools import setup 6 | 7 | readme = '' 8 | changes = '' 9 | 10 | setup_requires = [] 11 | install_requires = [ 12 | 'requests==2.22.0', 13 | 'eth-abi==2.1.0', 14 | 'pysha3==1.0.2', 15 | 'ecdsa==0.15', 16 | 'secp256k1==0.13.2', 17 | 'protobuf==3.11.3' 18 | ] 19 | 20 | # Get the requirements list 21 | with open('requirements.txt', 'r') as f: 22 | install_requires = f.read().splitlines() 23 | 24 | 25 | module_name = 'cita' 26 | mod = importlib.import_module(module_name) 27 | 28 | 29 | setup( 30 | name='cita_sdk_python', 31 | version=mod.__version__, 32 | description=mod.__description__, 33 | long_description=u"\n\n".join([readme, changes]), 34 | platforms=['Windows', 'Mac OS-X', 'Linux', 'Unix'], 35 | license='Apache License 2.0', 36 | classifiers=[ 37 | 'Intended Audience :: Developers', 38 | 'Development Status :: 4 - Beta', 39 | 'Operating System :: MacOS', 40 | 'Operating System :: Microsoft :: Windows', 41 | 'Operating System :: Unix', 42 | 'License :: OSI Approved :: Apache License, Version 2.0 (Apache-2.0)', 43 | 'Programming Language :: Python :: 3', 44 | 'Programming Language :: Python :: 3.7', 45 | 'Programming Language :: Python :: 3.8', 46 | ], 47 | keywords='cita blockchain', 48 | 49 | author=mod.__author__, 50 | author_email=mod.__email__, 51 | url=mod.__url__, 52 | 53 | packages=['cita'], 54 | package_dir={'': 'src'}, # tell distutils packages are under src 55 | python_requires='>=3.7, <4', 56 | setup_requires=setup_requires, 57 | install_requires=install_requires, 58 | 59 | include_package_data=True, # automatically include any data files it finds inside your package directories that are specified by your MANIFEST.in file 60 | zip_safe=False, # this project CANNOT be safely installed and run from a zip file 61 | ) 62 | -------------------------------------------------------------------------------- /src/cita/__init__.py: -------------------------------------------------------------------------------- 1 | from .sdk import CitaClient, ContractClass, ContractProxy 2 | from .util import join_param, equal_param, encode_param, decode_param, param_to_bytes, param_to_str, DEFAULT_QUOTA, LATEST_VERSION 3 | 4 | __version__ = '0.1.0' 5 | __author__ = 'Shen Lei' 6 | __description__ = 'CITA Python SDK.' 7 | __email__ = 'shenlei@funji.club' 8 | __url__ = 'https://github.com/citahub/cita-sdk-python' 9 | 10 | __all__ = ['CitaClient', 'ContractClass', 'ContractProxy', 11 | 'join_param', 'equal_param', 'encode_param', 'decode_param', 'param_to_bytes', 'param_to_str', 12 | 'DEFAULT_QUOTA'] 13 | -------------------------------------------------------------------------------- /src/cita/blockchain_pb2.py: -------------------------------------------------------------------------------- 1 | # Generated by the protocol buffer compiler. DO NOT EDIT! 2 | # source: blockchain.proto 3 | 4 | import sys 5 | _b = sys.version_info[0] < 3 and (lambda x: x) or ( 6 | lambda x: x.encode('latin1')) 7 | from google.protobuf.internal import enum_type_wrapper 8 | from google.protobuf import descriptor as _descriptor 9 | from google.protobuf import message as _message 10 | from google.protobuf import reflection as _reflection 11 | from google.protobuf import symbol_database as _symbol_database 12 | # @@protoc_insertion_point(imports) 13 | 14 | _sym_db = _symbol_database.Default() 15 | 16 | DESCRIPTOR = _descriptor.FileDescriptor( 17 | name='blockchain.proto', 18 | package='', 19 | syntax='proto3', 20 | serialized_options=None, 21 | serialized_pb=_b( 22 | '\n\x10\x62lockchain.proto\"2\n\x05Proof\x12\x0f\n\x07\x63ontent\x18\x01 \x01(\x0c\x12\x18\n\x04type\x18\x02 \x01(\x0e\x32\n.ProofType\"\xda\x01\n\x0b\x42lockHeader\x12\x10\n\x08prevhash\x18\x01 \x01(\x0c\x12\x11\n\ttimestamp\x18\x02 \x01(\x04\x12\x0e\n\x06height\x18\x03 \x01(\x04\x12\x12\n\nstate_root\x18\x04 \x01(\x0c\x12\x19\n\x11transactions_root\x18\x05 \x01(\x0c\x12\x15\n\rreceipts_root\x18\x06 \x01(\x0c\x12\x12\n\nquota_used\x18\x07 \x01(\x04\x12\x13\n\x0bquota_limit\x18\x08 \x01(\x04\x12\x15\n\x05proof\x18\t \x01(\x0b\x32\x06.Proof\x12\x10\n\x08proposer\x18\n \x01(\x0c\"&\n\x06Status\x12\x0c\n\x04hash\x18\x01 \x01(\x0c\x12\x0e\n\x06height\x18\x02 \x01(\x04\"\xb0\x01\n\x0f\x41\x63\x63ountGasLimit\x12\x1a\n\x12\x63ommon_quota_limit\x18\x01 \x01(\x04\x12\x46\n\x14specific_quota_limit\x18\x02 \x03(\x0b\x32(.AccountGasLimit.SpecificQuotaLimitEntry\x1a\x39\n\x17SpecificQuotaLimitEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x04:\x02\x38\x01\"p\n\nRichStatus\x12\x0c\n\x04hash\x18\x01 \x01(\x0c\x12\x0e\n\x06height\x18\x02 \x01(\x04\x12\r\n\x05nodes\x18\x03 \x03(\x0c\x12\x10\n\x08interval\x18\x04 \x01(\x04\x12\x0f\n\x07version\x18\x05 \x01(\r\x12\x12\n\nvalidators\x18\x06 \x03(\x0c\"\xb6\x01\n\x0bTransaction\x12\n\n\x02to\x18\x01 \x01(\t\x12\r\n\x05nonce\x18\x02 \x01(\t\x12\r\n\x05quota\x18\x03 \x01(\x04\x12\x19\n\x11valid_until_block\x18\x04 \x01(\x04\x12\x0c\n\x04\x64\x61ta\x18\x05 \x01(\x0c\x12\r\n\x05value\x18\x06 \x01(\x0c\x12\x10\n\x08\x63hain_id\x18\x07 \x01(\r\x12\x0f\n\x07version\x18\x08 \x01(\r\x12\r\n\x05to_v1\x18\t \x01(\x0c\x12\x13\n\x0b\x63hain_id_v1\x18\n \x01(\x0c\"f\n\x15UnverifiedTransaction\x12!\n\x0btransaction\x18\x01 \x01(\x0b\x32\x0c.Transaction\x12\x11\n\tsignature\x18\x02 \x01(\x0c\x12\x17\n\x06\x63rypto\x18\x03 \x01(\x0e\x32\x07.Crypto\"j\n\x11SignedTransaction\x12\x34\n\x14transaction_with_sig\x18\x01 \x01(\x0b\x32\x16.UnverifiedTransaction\x12\x0f\n\x07tx_hash\x18\x02 \x01(\x0c\x12\x0e\n\x06signer\x18\x03 \x01(\x0c\"5\n\tBlockBody\x12(\n\x0ctransactions\x18\x01 \x03(\x0b\x32\x12.SignedTransaction\"%\n\x10\x43ompactBlockBody\x12\x11\n\ttx_hashes\x18\x01 \x03(\x0c\"P\n\x05\x42lock\x12\x0f\n\x07version\x18\x01 \x01(\r\x12\x1c\n\x06header\x18\x02 \x01(\x0b\x32\x0c.BlockHeader\x12\x18\n\x04\x62ody\x18\x03 \x01(\x0b\x32\n.BlockBody\"^\n\x0c\x43ompactBlock\x12\x0f\n\x07version\x18\x01 \x01(\r\x12\x1c\n\x06header\x18\x02 \x01(\x0b\x32\x0c.BlockHeader\x12\x1f\n\x04\x62ody\x18\x03 \x01(\x0b\x32\x11.CompactBlockBody\"<\n\x0e\x42lockWithProof\x12\x13\n\x03\x62lk\x18\x01 \x01(\x0b\x32\x06.Block\x12\x15\n\x05proof\x18\x02 \x01(\x0b\x32\x06.Proof\"4\n\x08\x42lockTxs\x12\x0e\n\x06height\x18\x01 \x01(\x04\x12\x18\n\x04\x62ody\x18\x03 \x01(\x0b\x32\n.BlockBody\"3\n\tBlackList\x12\x12\n\nblack_list\x18\x01 \x03(\x0c\x12\x12\n\nclear_list\x18\x02 \x03(\x0c\"\x1d\n\x0bStateSignal\x12\x0e\n\x06height\x18\x01 \x01(\x04*2\n\tProofType\x12\x12\n\x0e\x41uthorityRound\x10\x00\x12\x08\n\x04Raft\x10\x01\x12\x07\n\x03\x42\x66t\x10\x02*#\n\x06\x43rypto\x12\x0b\n\x07\x44\x45\x46\x41ULT\x10\x00\x12\x0c\n\x08RESERVED\x10\x01\x62\x06proto3' 23 | )) 24 | 25 | _PROOFTYPE = _descriptor.EnumDescriptor( 26 | name='ProofType', 27 | full_name='ProofType', 28 | filename=None, 29 | file=DESCRIPTOR, 30 | values=[ 31 | _descriptor.EnumValueDescriptor( 32 | name='AuthorityRound', 33 | index=0, 34 | number=0, 35 | serialized_options=None, 36 | type=None), 37 | _descriptor.EnumValueDescriptor( 38 | name='Raft', index=1, number=1, serialized_options=None, 39 | type=None), 40 | _descriptor.EnumValueDescriptor( 41 | name='Bft', index=2, number=2, serialized_options=None, type=None), 42 | ], 43 | containing_type=None, 44 | serialized_options=None, 45 | serialized_start=1495, 46 | serialized_end=1545, 47 | ) 48 | _sym_db.RegisterEnumDescriptor(_PROOFTYPE) 49 | 50 | ProofType = enum_type_wrapper.EnumTypeWrapper(_PROOFTYPE) 51 | _CRYPTO = _descriptor.EnumDescriptor( 52 | name='Crypto', 53 | full_name='Crypto', 54 | filename=None, 55 | file=DESCRIPTOR, 56 | values=[ 57 | _descriptor.EnumValueDescriptor( 58 | name='DEFAULT', 59 | index=0, 60 | number=0, 61 | serialized_options=None, 62 | type=None), 63 | _descriptor.EnumValueDescriptor( 64 | name='RESERVED', 65 | index=1, 66 | number=1, 67 | serialized_options=None, 68 | type=None), 69 | ], 70 | containing_type=None, 71 | serialized_options=None, 72 | serialized_start=1547, 73 | serialized_end=1582, 74 | ) 75 | _sym_db.RegisterEnumDescriptor(_CRYPTO) 76 | 77 | Crypto = enum_type_wrapper.EnumTypeWrapper(_CRYPTO) 78 | AuthorityRound = 0 79 | Raft = 1 80 | Bft = 2 81 | DEFAULT = 0 82 | RESERVED = 1 83 | 84 | _PROOF = _descriptor.Descriptor( 85 | name='Proof', 86 | full_name='Proof', 87 | filename=None, 88 | file=DESCRIPTOR, 89 | containing_type=None, 90 | fields=[ 91 | _descriptor.FieldDescriptor( 92 | name='content', 93 | full_name='Proof.content', 94 | index=0, 95 | number=1, 96 | type=12, 97 | cpp_type=9, 98 | label=1, 99 | has_default_value=False, 100 | default_value=_b(""), 101 | message_type=None, 102 | enum_type=None, 103 | containing_type=None, 104 | is_extension=False, 105 | extension_scope=None, 106 | serialized_options=None, 107 | file=DESCRIPTOR), 108 | _descriptor.FieldDescriptor( 109 | name='type', 110 | full_name='Proof.type', 111 | index=1, 112 | number=2, 113 | type=14, 114 | cpp_type=8, 115 | label=1, 116 | has_default_value=False, 117 | default_value=0, 118 | message_type=None, 119 | enum_type=None, 120 | containing_type=None, 121 | is_extension=False, 122 | extension_scope=None, 123 | serialized_options=None, 124 | file=DESCRIPTOR), 125 | ], 126 | extensions=[], 127 | nested_types=[], 128 | enum_types=[], 129 | serialized_options=None, 130 | is_extendable=False, 131 | syntax='proto3', 132 | extension_ranges=[], 133 | oneofs=[], 134 | serialized_start=20, 135 | serialized_end=70, 136 | ) 137 | 138 | _BLOCKHEADER = _descriptor.Descriptor( 139 | name='BlockHeader', 140 | full_name='BlockHeader', 141 | filename=None, 142 | file=DESCRIPTOR, 143 | containing_type=None, 144 | fields=[ 145 | _descriptor.FieldDescriptor( 146 | name='prevhash', 147 | full_name='BlockHeader.prevhash', 148 | index=0, 149 | number=1, 150 | type=12, 151 | cpp_type=9, 152 | label=1, 153 | has_default_value=False, 154 | default_value=_b(""), 155 | message_type=None, 156 | enum_type=None, 157 | containing_type=None, 158 | is_extension=False, 159 | extension_scope=None, 160 | serialized_options=None, 161 | file=DESCRIPTOR), 162 | _descriptor.FieldDescriptor( 163 | name='timestamp', 164 | full_name='BlockHeader.timestamp', 165 | index=1, 166 | number=2, 167 | type=4, 168 | cpp_type=4, 169 | label=1, 170 | has_default_value=False, 171 | default_value=0, 172 | message_type=None, 173 | enum_type=None, 174 | containing_type=None, 175 | is_extension=False, 176 | extension_scope=None, 177 | serialized_options=None, 178 | file=DESCRIPTOR), 179 | _descriptor.FieldDescriptor( 180 | name='height', 181 | full_name='BlockHeader.height', 182 | index=2, 183 | number=3, 184 | type=4, 185 | cpp_type=4, 186 | label=1, 187 | has_default_value=False, 188 | default_value=0, 189 | message_type=None, 190 | enum_type=None, 191 | containing_type=None, 192 | is_extension=False, 193 | extension_scope=None, 194 | serialized_options=None, 195 | file=DESCRIPTOR), 196 | _descriptor.FieldDescriptor( 197 | name='state_root', 198 | full_name='BlockHeader.state_root', 199 | index=3, 200 | number=4, 201 | type=12, 202 | cpp_type=9, 203 | label=1, 204 | has_default_value=False, 205 | default_value=_b(""), 206 | message_type=None, 207 | enum_type=None, 208 | containing_type=None, 209 | is_extension=False, 210 | extension_scope=None, 211 | serialized_options=None, 212 | file=DESCRIPTOR), 213 | _descriptor.FieldDescriptor( 214 | name='transactions_root', 215 | full_name='BlockHeader.transactions_root', 216 | index=4, 217 | number=5, 218 | type=12, 219 | cpp_type=9, 220 | label=1, 221 | has_default_value=False, 222 | default_value=_b(""), 223 | message_type=None, 224 | enum_type=None, 225 | containing_type=None, 226 | is_extension=False, 227 | extension_scope=None, 228 | serialized_options=None, 229 | file=DESCRIPTOR), 230 | _descriptor.FieldDescriptor( 231 | name='receipts_root', 232 | full_name='BlockHeader.receipts_root', 233 | index=5, 234 | number=6, 235 | type=12, 236 | cpp_type=9, 237 | label=1, 238 | has_default_value=False, 239 | default_value=_b(""), 240 | message_type=None, 241 | enum_type=None, 242 | containing_type=None, 243 | is_extension=False, 244 | extension_scope=None, 245 | serialized_options=None, 246 | file=DESCRIPTOR), 247 | _descriptor.FieldDescriptor( 248 | name='quota_used', 249 | full_name='BlockHeader.quota_used', 250 | index=6, 251 | number=7, 252 | type=4, 253 | cpp_type=4, 254 | label=1, 255 | has_default_value=False, 256 | default_value=0, 257 | message_type=None, 258 | enum_type=None, 259 | containing_type=None, 260 | is_extension=False, 261 | extension_scope=None, 262 | serialized_options=None, 263 | file=DESCRIPTOR), 264 | _descriptor.FieldDescriptor( 265 | name='quota_limit', 266 | full_name='BlockHeader.quota_limit', 267 | index=7, 268 | number=8, 269 | type=4, 270 | cpp_type=4, 271 | label=1, 272 | has_default_value=False, 273 | default_value=0, 274 | message_type=None, 275 | enum_type=None, 276 | containing_type=None, 277 | is_extension=False, 278 | extension_scope=None, 279 | serialized_options=None, 280 | file=DESCRIPTOR), 281 | _descriptor.FieldDescriptor( 282 | name='proof', 283 | full_name='BlockHeader.proof', 284 | index=8, 285 | number=9, 286 | type=11, 287 | cpp_type=10, 288 | label=1, 289 | has_default_value=False, 290 | default_value=None, 291 | message_type=None, 292 | enum_type=None, 293 | containing_type=None, 294 | is_extension=False, 295 | extension_scope=None, 296 | serialized_options=None, 297 | file=DESCRIPTOR), 298 | _descriptor.FieldDescriptor( 299 | name='proposer', 300 | full_name='BlockHeader.proposer', 301 | index=9, 302 | number=10, 303 | type=12, 304 | cpp_type=9, 305 | label=1, 306 | has_default_value=False, 307 | default_value=_b(""), 308 | message_type=None, 309 | enum_type=None, 310 | containing_type=None, 311 | is_extension=False, 312 | extension_scope=None, 313 | serialized_options=None, 314 | file=DESCRIPTOR), 315 | ], 316 | extensions=[], 317 | nested_types=[], 318 | enum_types=[], 319 | serialized_options=None, 320 | is_extendable=False, 321 | syntax='proto3', 322 | extension_ranges=[], 323 | oneofs=[], 324 | serialized_start=73, 325 | serialized_end=291, 326 | ) 327 | 328 | _STATUS = _descriptor.Descriptor( 329 | name='Status', 330 | full_name='Status', 331 | filename=None, 332 | file=DESCRIPTOR, 333 | containing_type=None, 334 | fields=[ 335 | _descriptor.FieldDescriptor( 336 | name='hash', 337 | full_name='Status.hash', 338 | index=0, 339 | number=1, 340 | type=12, 341 | cpp_type=9, 342 | label=1, 343 | has_default_value=False, 344 | default_value=_b(""), 345 | message_type=None, 346 | enum_type=None, 347 | containing_type=None, 348 | is_extension=False, 349 | extension_scope=None, 350 | serialized_options=None, 351 | file=DESCRIPTOR), 352 | _descriptor.FieldDescriptor( 353 | name='height', 354 | full_name='Status.height', 355 | index=1, 356 | number=2, 357 | type=4, 358 | cpp_type=4, 359 | label=1, 360 | has_default_value=False, 361 | default_value=0, 362 | message_type=None, 363 | enum_type=None, 364 | containing_type=None, 365 | is_extension=False, 366 | extension_scope=None, 367 | serialized_options=None, 368 | file=DESCRIPTOR), 369 | ], 370 | extensions=[], 371 | nested_types=[], 372 | enum_types=[], 373 | serialized_options=None, 374 | is_extendable=False, 375 | syntax='proto3', 376 | extension_ranges=[], 377 | oneofs=[], 378 | serialized_start=293, 379 | serialized_end=331, 380 | ) 381 | 382 | _ACCOUNTGASLIMIT_SPECIFICQUOTALIMITENTRY = _descriptor.Descriptor( 383 | name='SpecificQuotaLimitEntry', 384 | full_name='AccountGasLimit.SpecificQuotaLimitEntry', 385 | filename=None, 386 | file=DESCRIPTOR, 387 | containing_type=None, 388 | fields=[ 389 | _descriptor.FieldDescriptor( 390 | name='key', 391 | full_name='AccountGasLimit.SpecificQuotaLimitEntry.key', 392 | index=0, 393 | number=1, 394 | type=9, 395 | cpp_type=9, 396 | label=1, 397 | has_default_value=False, 398 | default_value=_b("").decode('utf-8'), 399 | message_type=None, 400 | enum_type=None, 401 | containing_type=None, 402 | is_extension=False, 403 | extension_scope=None, 404 | serialized_options=None, 405 | file=DESCRIPTOR), 406 | _descriptor.FieldDescriptor( 407 | name='value', 408 | full_name='AccountGasLimit.SpecificQuotaLimitEntry.value', 409 | index=1, 410 | number=2, 411 | type=4, 412 | cpp_type=4, 413 | label=1, 414 | has_default_value=False, 415 | default_value=0, 416 | message_type=None, 417 | enum_type=None, 418 | containing_type=None, 419 | is_extension=False, 420 | extension_scope=None, 421 | serialized_options=None, 422 | file=DESCRIPTOR), 423 | ], 424 | extensions=[], 425 | nested_types=[], 426 | enum_types=[], 427 | serialized_options=_b('8\001'), 428 | is_extendable=False, 429 | syntax='proto3', 430 | extension_ranges=[], 431 | oneofs=[], 432 | serialized_start=453, 433 | serialized_end=510, 434 | ) 435 | 436 | _ACCOUNTGASLIMIT = _descriptor.Descriptor( 437 | name='AccountGasLimit', 438 | full_name='AccountGasLimit', 439 | filename=None, 440 | file=DESCRIPTOR, 441 | containing_type=None, 442 | fields=[ 443 | _descriptor.FieldDescriptor( 444 | name='common_quota_limit', 445 | full_name='AccountGasLimit.common_quota_limit', 446 | index=0, 447 | number=1, 448 | type=4, 449 | cpp_type=4, 450 | label=1, 451 | has_default_value=False, 452 | default_value=0, 453 | message_type=None, 454 | enum_type=None, 455 | containing_type=None, 456 | is_extension=False, 457 | extension_scope=None, 458 | serialized_options=None, 459 | file=DESCRIPTOR), 460 | _descriptor.FieldDescriptor( 461 | name='specific_quota_limit', 462 | full_name='AccountGasLimit.specific_quota_limit', 463 | index=1, 464 | number=2, 465 | type=11, 466 | cpp_type=10, 467 | label=3, 468 | has_default_value=False, 469 | default_value=[], 470 | message_type=None, 471 | enum_type=None, 472 | containing_type=None, 473 | is_extension=False, 474 | extension_scope=None, 475 | serialized_options=None, 476 | file=DESCRIPTOR), 477 | ], 478 | extensions=[], 479 | nested_types=[ 480 | _ACCOUNTGASLIMIT_SPECIFICQUOTALIMITENTRY, 481 | ], 482 | enum_types=[], 483 | serialized_options=None, 484 | is_extendable=False, 485 | syntax='proto3', 486 | extension_ranges=[], 487 | oneofs=[], 488 | serialized_start=334, 489 | serialized_end=510, 490 | ) 491 | 492 | _RICHSTATUS = _descriptor.Descriptor( 493 | name='RichStatus', 494 | full_name='RichStatus', 495 | filename=None, 496 | file=DESCRIPTOR, 497 | containing_type=None, 498 | fields=[ 499 | _descriptor.FieldDescriptor( 500 | name='hash', 501 | full_name='RichStatus.hash', 502 | index=0, 503 | number=1, 504 | type=12, 505 | cpp_type=9, 506 | label=1, 507 | has_default_value=False, 508 | default_value=_b(""), 509 | message_type=None, 510 | enum_type=None, 511 | containing_type=None, 512 | is_extension=False, 513 | extension_scope=None, 514 | serialized_options=None, 515 | file=DESCRIPTOR), 516 | _descriptor.FieldDescriptor( 517 | name='height', 518 | full_name='RichStatus.height', 519 | index=1, 520 | number=2, 521 | type=4, 522 | cpp_type=4, 523 | label=1, 524 | has_default_value=False, 525 | default_value=0, 526 | message_type=None, 527 | enum_type=None, 528 | containing_type=None, 529 | is_extension=False, 530 | extension_scope=None, 531 | serialized_options=None, 532 | file=DESCRIPTOR), 533 | _descriptor.FieldDescriptor( 534 | name='nodes', 535 | full_name='RichStatus.nodes', 536 | index=2, 537 | number=3, 538 | type=12, 539 | cpp_type=9, 540 | label=3, 541 | has_default_value=False, 542 | default_value=[], 543 | message_type=None, 544 | enum_type=None, 545 | containing_type=None, 546 | is_extension=False, 547 | extension_scope=None, 548 | serialized_options=None, 549 | file=DESCRIPTOR), 550 | _descriptor.FieldDescriptor( 551 | name='interval', 552 | full_name='RichStatus.interval', 553 | index=3, 554 | number=4, 555 | type=4, 556 | cpp_type=4, 557 | label=1, 558 | has_default_value=False, 559 | default_value=0, 560 | message_type=None, 561 | enum_type=None, 562 | containing_type=None, 563 | is_extension=False, 564 | extension_scope=None, 565 | serialized_options=None, 566 | file=DESCRIPTOR), 567 | _descriptor.FieldDescriptor( 568 | name='version', 569 | full_name='RichStatus.version', 570 | index=4, 571 | number=5, 572 | type=13, 573 | cpp_type=3, 574 | label=1, 575 | has_default_value=False, 576 | default_value=0, 577 | message_type=None, 578 | enum_type=None, 579 | containing_type=None, 580 | is_extension=False, 581 | extension_scope=None, 582 | serialized_options=None, 583 | file=DESCRIPTOR), 584 | _descriptor.FieldDescriptor( 585 | name='validators', 586 | full_name='RichStatus.validators', 587 | index=5, 588 | number=6, 589 | type=12, 590 | cpp_type=9, 591 | label=3, 592 | has_default_value=False, 593 | default_value=[], 594 | message_type=None, 595 | enum_type=None, 596 | containing_type=None, 597 | is_extension=False, 598 | extension_scope=None, 599 | serialized_options=None, 600 | file=DESCRIPTOR), 601 | ], 602 | extensions=[], 603 | nested_types=[], 604 | enum_types=[], 605 | serialized_options=None, 606 | is_extendable=False, 607 | syntax='proto3', 608 | extension_ranges=[], 609 | oneofs=[], 610 | serialized_start=512, 611 | serialized_end=624, 612 | ) 613 | 614 | _TRANSACTION = _descriptor.Descriptor( 615 | name='Transaction', 616 | full_name='Transaction', 617 | filename=None, 618 | file=DESCRIPTOR, 619 | containing_type=None, 620 | fields=[ 621 | _descriptor.FieldDescriptor( 622 | name='to', 623 | full_name='Transaction.to', 624 | index=0, 625 | number=1, 626 | type=9, 627 | cpp_type=9, 628 | label=1, 629 | has_default_value=False, 630 | default_value=_b("").decode('utf-8'), 631 | message_type=None, 632 | enum_type=None, 633 | containing_type=None, 634 | is_extension=False, 635 | extension_scope=None, 636 | serialized_options=None, 637 | file=DESCRIPTOR), 638 | _descriptor.FieldDescriptor( 639 | name='nonce', 640 | full_name='Transaction.nonce', 641 | index=1, 642 | number=2, 643 | type=9, 644 | cpp_type=9, 645 | label=1, 646 | has_default_value=False, 647 | default_value=_b("").decode('utf-8'), 648 | message_type=None, 649 | enum_type=None, 650 | containing_type=None, 651 | is_extension=False, 652 | extension_scope=None, 653 | serialized_options=None, 654 | file=DESCRIPTOR), 655 | _descriptor.FieldDescriptor( 656 | name='quota', 657 | full_name='Transaction.quota', 658 | index=2, 659 | number=3, 660 | type=4, 661 | cpp_type=4, 662 | label=1, 663 | has_default_value=False, 664 | default_value=0, 665 | message_type=None, 666 | enum_type=None, 667 | containing_type=None, 668 | is_extension=False, 669 | extension_scope=None, 670 | serialized_options=None, 671 | file=DESCRIPTOR), 672 | _descriptor.FieldDescriptor( 673 | name='valid_until_block', 674 | full_name='Transaction.valid_until_block', 675 | index=3, 676 | number=4, 677 | type=4, 678 | cpp_type=4, 679 | label=1, 680 | has_default_value=False, 681 | default_value=0, 682 | message_type=None, 683 | enum_type=None, 684 | containing_type=None, 685 | is_extension=False, 686 | extension_scope=None, 687 | serialized_options=None, 688 | file=DESCRIPTOR), 689 | _descriptor.FieldDescriptor( 690 | name='data', 691 | full_name='Transaction.data', 692 | index=4, 693 | number=5, 694 | type=12, 695 | cpp_type=9, 696 | label=1, 697 | has_default_value=False, 698 | default_value=_b(""), 699 | message_type=None, 700 | enum_type=None, 701 | containing_type=None, 702 | is_extension=False, 703 | extension_scope=None, 704 | serialized_options=None, 705 | file=DESCRIPTOR), 706 | _descriptor.FieldDescriptor( 707 | name='value', 708 | full_name='Transaction.value', 709 | index=5, 710 | number=6, 711 | type=12, 712 | cpp_type=9, 713 | label=1, 714 | has_default_value=False, 715 | default_value=_b(""), 716 | message_type=None, 717 | enum_type=None, 718 | containing_type=None, 719 | is_extension=False, 720 | extension_scope=None, 721 | serialized_options=None, 722 | file=DESCRIPTOR), 723 | _descriptor.FieldDescriptor( 724 | name='chain_id', 725 | full_name='Transaction.chain_id', 726 | index=6, 727 | number=7, 728 | type=13, 729 | cpp_type=3, 730 | label=1, 731 | has_default_value=False, 732 | default_value=0, 733 | message_type=None, 734 | enum_type=None, 735 | containing_type=None, 736 | is_extension=False, 737 | extension_scope=None, 738 | serialized_options=None, 739 | file=DESCRIPTOR), 740 | _descriptor.FieldDescriptor( 741 | name='version', 742 | full_name='Transaction.version', 743 | index=7, 744 | number=8, 745 | type=13, 746 | cpp_type=3, 747 | label=1, 748 | has_default_value=False, 749 | default_value=0, 750 | message_type=None, 751 | enum_type=None, 752 | containing_type=None, 753 | is_extension=False, 754 | extension_scope=None, 755 | serialized_options=None, 756 | file=DESCRIPTOR), 757 | _descriptor.FieldDescriptor( 758 | name='to_v1', 759 | full_name='Transaction.to_v1', 760 | index=8, 761 | number=9, 762 | type=12, 763 | cpp_type=9, 764 | label=1, 765 | has_default_value=False, 766 | default_value=_b(""), 767 | message_type=None, 768 | enum_type=None, 769 | containing_type=None, 770 | is_extension=False, 771 | extension_scope=None, 772 | serialized_options=None, 773 | file=DESCRIPTOR), 774 | _descriptor.FieldDescriptor( 775 | name='chain_id_v1', 776 | full_name='Transaction.chain_id_v1', 777 | index=9, 778 | number=10, 779 | type=12, 780 | cpp_type=9, 781 | label=1, 782 | has_default_value=False, 783 | default_value=_b(""), 784 | message_type=None, 785 | enum_type=None, 786 | containing_type=None, 787 | is_extension=False, 788 | extension_scope=None, 789 | serialized_options=None, 790 | file=DESCRIPTOR), 791 | ], 792 | extensions=[], 793 | nested_types=[], 794 | enum_types=[], 795 | serialized_options=None, 796 | is_extendable=False, 797 | syntax='proto3', 798 | extension_ranges=[], 799 | oneofs=[], 800 | serialized_start=627, 801 | serialized_end=809, 802 | ) 803 | 804 | _UNVERIFIEDTRANSACTION = _descriptor.Descriptor( 805 | name='UnverifiedTransaction', 806 | full_name='UnverifiedTransaction', 807 | filename=None, 808 | file=DESCRIPTOR, 809 | containing_type=None, 810 | fields=[ 811 | _descriptor.FieldDescriptor( 812 | name='transaction', 813 | full_name='UnverifiedTransaction.transaction', 814 | index=0, 815 | number=1, 816 | type=11, 817 | cpp_type=10, 818 | label=1, 819 | has_default_value=False, 820 | default_value=None, 821 | message_type=None, 822 | enum_type=None, 823 | containing_type=None, 824 | is_extension=False, 825 | extension_scope=None, 826 | serialized_options=None, 827 | file=DESCRIPTOR), 828 | _descriptor.FieldDescriptor( 829 | name='signature', 830 | full_name='UnverifiedTransaction.signature', 831 | index=1, 832 | number=2, 833 | type=12, 834 | cpp_type=9, 835 | label=1, 836 | has_default_value=False, 837 | default_value=_b(""), 838 | message_type=None, 839 | enum_type=None, 840 | containing_type=None, 841 | is_extension=False, 842 | extension_scope=None, 843 | serialized_options=None, 844 | file=DESCRIPTOR), 845 | _descriptor.FieldDescriptor( 846 | name='crypto', 847 | full_name='UnverifiedTransaction.crypto', 848 | index=2, 849 | number=3, 850 | type=14, 851 | cpp_type=8, 852 | label=1, 853 | has_default_value=False, 854 | default_value=0, 855 | message_type=None, 856 | enum_type=None, 857 | containing_type=None, 858 | is_extension=False, 859 | extension_scope=None, 860 | serialized_options=None, 861 | file=DESCRIPTOR), 862 | ], 863 | extensions=[], 864 | nested_types=[], 865 | enum_types=[], 866 | serialized_options=None, 867 | is_extendable=False, 868 | syntax='proto3', 869 | extension_ranges=[], 870 | oneofs=[], 871 | serialized_start=811, 872 | serialized_end=913, 873 | ) 874 | 875 | _SIGNEDTRANSACTION = _descriptor.Descriptor( 876 | name='SignedTransaction', 877 | full_name='SignedTransaction', 878 | filename=None, 879 | file=DESCRIPTOR, 880 | containing_type=None, 881 | fields=[ 882 | _descriptor.FieldDescriptor( 883 | name='transaction_with_sig', 884 | full_name='SignedTransaction.transaction_with_sig', 885 | index=0, 886 | number=1, 887 | type=11, 888 | cpp_type=10, 889 | label=1, 890 | has_default_value=False, 891 | default_value=None, 892 | message_type=None, 893 | enum_type=None, 894 | containing_type=None, 895 | is_extension=False, 896 | extension_scope=None, 897 | serialized_options=None, 898 | file=DESCRIPTOR), 899 | _descriptor.FieldDescriptor( 900 | name='tx_hash', 901 | full_name='SignedTransaction.tx_hash', 902 | index=1, 903 | number=2, 904 | type=12, 905 | cpp_type=9, 906 | label=1, 907 | has_default_value=False, 908 | default_value=_b(""), 909 | message_type=None, 910 | enum_type=None, 911 | containing_type=None, 912 | is_extension=False, 913 | extension_scope=None, 914 | serialized_options=None, 915 | file=DESCRIPTOR), 916 | _descriptor.FieldDescriptor( 917 | name='signer', 918 | full_name='SignedTransaction.signer', 919 | index=2, 920 | number=3, 921 | type=12, 922 | cpp_type=9, 923 | label=1, 924 | has_default_value=False, 925 | default_value=_b(""), 926 | message_type=None, 927 | enum_type=None, 928 | containing_type=None, 929 | is_extension=False, 930 | extension_scope=None, 931 | serialized_options=None, 932 | file=DESCRIPTOR), 933 | ], 934 | extensions=[], 935 | nested_types=[], 936 | enum_types=[], 937 | serialized_options=None, 938 | is_extendable=False, 939 | syntax='proto3', 940 | extension_ranges=[], 941 | oneofs=[], 942 | serialized_start=915, 943 | serialized_end=1021, 944 | ) 945 | 946 | _BLOCKBODY = _descriptor.Descriptor( 947 | name='BlockBody', 948 | full_name='BlockBody', 949 | filename=None, 950 | file=DESCRIPTOR, 951 | containing_type=None, 952 | fields=[ 953 | _descriptor.FieldDescriptor( 954 | name='transactions', 955 | full_name='BlockBody.transactions', 956 | index=0, 957 | number=1, 958 | type=11, 959 | cpp_type=10, 960 | label=3, 961 | has_default_value=False, 962 | default_value=[], 963 | message_type=None, 964 | enum_type=None, 965 | containing_type=None, 966 | is_extension=False, 967 | extension_scope=None, 968 | serialized_options=None, 969 | file=DESCRIPTOR), 970 | ], 971 | extensions=[], 972 | nested_types=[], 973 | enum_types=[], 974 | serialized_options=None, 975 | is_extendable=False, 976 | syntax='proto3', 977 | extension_ranges=[], 978 | oneofs=[], 979 | serialized_start=1023, 980 | serialized_end=1076, 981 | ) 982 | 983 | _COMPACTBLOCKBODY = _descriptor.Descriptor( 984 | name='CompactBlockBody', 985 | full_name='CompactBlockBody', 986 | filename=None, 987 | file=DESCRIPTOR, 988 | containing_type=None, 989 | fields=[ 990 | _descriptor.FieldDescriptor( 991 | name='tx_hashes', 992 | full_name='CompactBlockBody.tx_hashes', 993 | index=0, 994 | number=1, 995 | type=12, 996 | cpp_type=9, 997 | label=3, 998 | has_default_value=False, 999 | default_value=[], 1000 | message_type=None, 1001 | enum_type=None, 1002 | containing_type=None, 1003 | is_extension=False, 1004 | extension_scope=None, 1005 | serialized_options=None, 1006 | file=DESCRIPTOR), 1007 | ], 1008 | extensions=[], 1009 | nested_types=[], 1010 | enum_types=[], 1011 | serialized_options=None, 1012 | is_extendable=False, 1013 | syntax='proto3', 1014 | extension_ranges=[], 1015 | oneofs=[], 1016 | serialized_start=1078, 1017 | serialized_end=1115, 1018 | ) 1019 | 1020 | _BLOCK = _descriptor.Descriptor( 1021 | name='Block', 1022 | full_name='Block', 1023 | filename=None, 1024 | file=DESCRIPTOR, 1025 | containing_type=None, 1026 | fields=[ 1027 | _descriptor.FieldDescriptor( 1028 | name='version', 1029 | full_name='Block.version', 1030 | index=0, 1031 | number=1, 1032 | type=13, 1033 | cpp_type=3, 1034 | label=1, 1035 | has_default_value=False, 1036 | default_value=0, 1037 | message_type=None, 1038 | enum_type=None, 1039 | containing_type=None, 1040 | is_extension=False, 1041 | extension_scope=None, 1042 | serialized_options=None, 1043 | file=DESCRIPTOR), 1044 | _descriptor.FieldDescriptor( 1045 | name='header', 1046 | full_name='Block.header', 1047 | index=1, 1048 | number=2, 1049 | type=11, 1050 | cpp_type=10, 1051 | label=1, 1052 | has_default_value=False, 1053 | default_value=None, 1054 | message_type=None, 1055 | enum_type=None, 1056 | containing_type=None, 1057 | is_extension=False, 1058 | extension_scope=None, 1059 | serialized_options=None, 1060 | file=DESCRIPTOR), 1061 | _descriptor.FieldDescriptor( 1062 | name='body', 1063 | full_name='Block.body', 1064 | index=2, 1065 | number=3, 1066 | type=11, 1067 | cpp_type=10, 1068 | label=1, 1069 | has_default_value=False, 1070 | default_value=None, 1071 | message_type=None, 1072 | enum_type=None, 1073 | containing_type=None, 1074 | is_extension=False, 1075 | extension_scope=None, 1076 | serialized_options=None, 1077 | file=DESCRIPTOR), 1078 | ], 1079 | extensions=[], 1080 | nested_types=[], 1081 | enum_types=[], 1082 | serialized_options=None, 1083 | is_extendable=False, 1084 | syntax='proto3', 1085 | extension_ranges=[], 1086 | oneofs=[], 1087 | serialized_start=1117, 1088 | serialized_end=1197, 1089 | ) 1090 | 1091 | _COMPACTBLOCK = _descriptor.Descriptor( 1092 | name='CompactBlock', 1093 | full_name='CompactBlock', 1094 | filename=None, 1095 | file=DESCRIPTOR, 1096 | containing_type=None, 1097 | fields=[ 1098 | _descriptor.FieldDescriptor( 1099 | name='version', 1100 | full_name='CompactBlock.version', 1101 | index=0, 1102 | number=1, 1103 | type=13, 1104 | cpp_type=3, 1105 | label=1, 1106 | has_default_value=False, 1107 | default_value=0, 1108 | message_type=None, 1109 | enum_type=None, 1110 | containing_type=None, 1111 | is_extension=False, 1112 | extension_scope=None, 1113 | serialized_options=None, 1114 | file=DESCRIPTOR), 1115 | _descriptor.FieldDescriptor( 1116 | name='header', 1117 | full_name='CompactBlock.header', 1118 | index=1, 1119 | number=2, 1120 | type=11, 1121 | cpp_type=10, 1122 | label=1, 1123 | has_default_value=False, 1124 | default_value=None, 1125 | message_type=None, 1126 | enum_type=None, 1127 | containing_type=None, 1128 | is_extension=False, 1129 | extension_scope=None, 1130 | serialized_options=None, 1131 | file=DESCRIPTOR), 1132 | _descriptor.FieldDescriptor( 1133 | name='body', 1134 | full_name='CompactBlock.body', 1135 | index=2, 1136 | number=3, 1137 | type=11, 1138 | cpp_type=10, 1139 | label=1, 1140 | has_default_value=False, 1141 | default_value=None, 1142 | message_type=None, 1143 | enum_type=None, 1144 | containing_type=None, 1145 | is_extension=False, 1146 | extension_scope=None, 1147 | serialized_options=None, 1148 | file=DESCRIPTOR), 1149 | ], 1150 | extensions=[], 1151 | nested_types=[], 1152 | enum_types=[], 1153 | serialized_options=None, 1154 | is_extendable=False, 1155 | syntax='proto3', 1156 | extension_ranges=[], 1157 | oneofs=[], 1158 | serialized_start=1199, 1159 | serialized_end=1293, 1160 | ) 1161 | 1162 | _BLOCKWITHPROOF = _descriptor.Descriptor( 1163 | name='BlockWithProof', 1164 | full_name='BlockWithProof', 1165 | filename=None, 1166 | file=DESCRIPTOR, 1167 | containing_type=None, 1168 | fields=[ 1169 | _descriptor.FieldDescriptor( 1170 | name='blk', 1171 | full_name='BlockWithProof.blk', 1172 | index=0, 1173 | number=1, 1174 | type=11, 1175 | cpp_type=10, 1176 | label=1, 1177 | has_default_value=False, 1178 | default_value=None, 1179 | message_type=None, 1180 | enum_type=None, 1181 | containing_type=None, 1182 | is_extension=False, 1183 | extension_scope=None, 1184 | serialized_options=None, 1185 | file=DESCRIPTOR), 1186 | _descriptor.FieldDescriptor( 1187 | name='proof', 1188 | full_name='BlockWithProof.proof', 1189 | index=1, 1190 | number=2, 1191 | type=11, 1192 | cpp_type=10, 1193 | label=1, 1194 | has_default_value=False, 1195 | default_value=None, 1196 | message_type=None, 1197 | enum_type=None, 1198 | containing_type=None, 1199 | is_extension=False, 1200 | extension_scope=None, 1201 | serialized_options=None, 1202 | file=DESCRIPTOR), 1203 | ], 1204 | extensions=[], 1205 | nested_types=[], 1206 | enum_types=[], 1207 | serialized_options=None, 1208 | is_extendable=False, 1209 | syntax='proto3', 1210 | extension_ranges=[], 1211 | oneofs=[], 1212 | serialized_start=1295, 1213 | serialized_end=1355, 1214 | ) 1215 | 1216 | _BLOCKTXS = _descriptor.Descriptor( 1217 | name='BlockTxs', 1218 | full_name='BlockTxs', 1219 | filename=None, 1220 | file=DESCRIPTOR, 1221 | containing_type=None, 1222 | fields=[ 1223 | _descriptor.FieldDescriptor( 1224 | name='height', 1225 | full_name='BlockTxs.height', 1226 | index=0, 1227 | number=1, 1228 | type=4, 1229 | cpp_type=4, 1230 | label=1, 1231 | has_default_value=False, 1232 | default_value=0, 1233 | message_type=None, 1234 | enum_type=None, 1235 | containing_type=None, 1236 | is_extension=False, 1237 | extension_scope=None, 1238 | serialized_options=None, 1239 | file=DESCRIPTOR), 1240 | _descriptor.FieldDescriptor( 1241 | name='body', 1242 | full_name='BlockTxs.body', 1243 | index=1, 1244 | number=3, 1245 | type=11, 1246 | cpp_type=10, 1247 | label=1, 1248 | has_default_value=False, 1249 | default_value=None, 1250 | message_type=None, 1251 | enum_type=None, 1252 | containing_type=None, 1253 | is_extension=False, 1254 | extension_scope=None, 1255 | serialized_options=None, 1256 | file=DESCRIPTOR), 1257 | ], 1258 | extensions=[], 1259 | nested_types=[], 1260 | enum_types=[], 1261 | serialized_options=None, 1262 | is_extendable=False, 1263 | syntax='proto3', 1264 | extension_ranges=[], 1265 | oneofs=[], 1266 | serialized_start=1357, 1267 | serialized_end=1409, 1268 | ) 1269 | 1270 | _BLACKLIST = _descriptor.Descriptor( 1271 | name='BlackList', 1272 | full_name='BlackList', 1273 | filename=None, 1274 | file=DESCRIPTOR, 1275 | containing_type=None, 1276 | fields=[ 1277 | _descriptor.FieldDescriptor( 1278 | name='black_list', 1279 | full_name='BlackList.black_list', 1280 | index=0, 1281 | number=1, 1282 | type=12, 1283 | cpp_type=9, 1284 | label=3, 1285 | has_default_value=False, 1286 | default_value=[], 1287 | message_type=None, 1288 | enum_type=None, 1289 | containing_type=None, 1290 | is_extension=False, 1291 | extension_scope=None, 1292 | serialized_options=None, 1293 | file=DESCRIPTOR), 1294 | _descriptor.FieldDescriptor( 1295 | name='clear_list', 1296 | full_name='BlackList.clear_list', 1297 | index=1, 1298 | number=2, 1299 | type=12, 1300 | cpp_type=9, 1301 | label=3, 1302 | has_default_value=False, 1303 | default_value=[], 1304 | message_type=None, 1305 | enum_type=None, 1306 | containing_type=None, 1307 | is_extension=False, 1308 | extension_scope=None, 1309 | serialized_options=None, 1310 | file=DESCRIPTOR), 1311 | ], 1312 | extensions=[], 1313 | nested_types=[], 1314 | enum_types=[], 1315 | serialized_options=None, 1316 | is_extendable=False, 1317 | syntax='proto3', 1318 | extension_ranges=[], 1319 | oneofs=[], 1320 | serialized_start=1411, 1321 | serialized_end=1462, 1322 | ) 1323 | 1324 | _STATESIGNAL = _descriptor.Descriptor( 1325 | name='StateSignal', 1326 | full_name='StateSignal', 1327 | filename=None, 1328 | file=DESCRIPTOR, 1329 | containing_type=None, 1330 | fields=[ 1331 | _descriptor.FieldDescriptor( 1332 | name='height', 1333 | full_name='StateSignal.height', 1334 | index=0, 1335 | number=1, 1336 | type=4, 1337 | cpp_type=4, 1338 | label=1, 1339 | has_default_value=False, 1340 | default_value=0, 1341 | message_type=None, 1342 | enum_type=None, 1343 | containing_type=None, 1344 | is_extension=False, 1345 | extension_scope=None, 1346 | serialized_options=None, 1347 | file=DESCRIPTOR), 1348 | ], 1349 | extensions=[], 1350 | nested_types=[], 1351 | enum_types=[], 1352 | serialized_options=None, 1353 | is_extendable=False, 1354 | syntax='proto3', 1355 | extension_ranges=[], 1356 | oneofs=[], 1357 | serialized_start=1464, 1358 | serialized_end=1493, 1359 | ) 1360 | 1361 | _PROOF.fields_by_name['type'].enum_type = _PROOFTYPE 1362 | _BLOCKHEADER.fields_by_name['proof'].message_type = _PROOF 1363 | _ACCOUNTGASLIMIT_SPECIFICQUOTALIMITENTRY.containing_type = _ACCOUNTGASLIMIT 1364 | _ACCOUNTGASLIMIT.fields_by_name[ 1365 | 'specific_quota_limit'].message_type = _ACCOUNTGASLIMIT_SPECIFICQUOTALIMITENTRY 1366 | _UNVERIFIEDTRANSACTION.fields_by_name[ 1367 | 'transaction'].message_type = _TRANSACTION 1368 | _UNVERIFIEDTRANSACTION.fields_by_name['crypto'].enum_type = _CRYPTO 1369 | _SIGNEDTRANSACTION.fields_by_name[ 1370 | 'transaction_with_sig'].message_type = _UNVERIFIEDTRANSACTION 1371 | _BLOCKBODY.fields_by_name['transactions'].message_type = _SIGNEDTRANSACTION 1372 | _BLOCK.fields_by_name['header'].message_type = _BLOCKHEADER 1373 | _BLOCK.fields_by_name['body'].message_type = _BLOCKBODY 1374 | _COMPACTBLOCK.fields_by_name['header'].message_type = _BLOCKHEADER 1375 | _COMPACTBLOCK.fields_by_name['body'].message_type = _COMPACTBLOCKBODY 1376 | _BLOCKWITHPROOF.fields_by_name['blk'].message_type = _BLOCK 1377 | _BLOCKWITHPROOF.fields_by_name['proof'].message_type = _PROOF 1378 | _BLOCKTXS.fields_by_name['body'].message_type = _BLOCKBODY 1379 | DESCRIPTOR.message_types_by_name['Proof'] = _PROOF 1380 | DESCRIPTOR.message_types_by_name['BlockHeader'] = _BLOCKHEADER 1381 | DESCRIPTOR.message_types_by_name['Status'] = _STATUS 1382 | DESCRIPTOR.message_types_by_name['AccountGasLimit'] = _ACCOUNTGASLIMIT 1383 | DESCRIPTOR.message_types_by_name['RichStatus'] = _RICHSTATUS 1384 | DESCRIPTOR.message_types_by_name['Transaction'] = _TRANSACTION 1385 | DESCRIPTOR.message_types_by_name[ 1386 | 'UnverifiedTransaction'] = _UNVERIFIEDTRANSACTION 1387 | DESCRIPTOR.message_types_by_name['SignedTransaction'] = _SIGNEDTRANSACTION 1388 | DESCRIPTOR.message_types_by_name['BlockBody'] = _BLOCKBODY 1389 | DESCRIPTOR.message_types_by_name['CompactBlockBody'] = _COMPACTBLOCKBODY 1390 | DESCRIPTOR.message_types_by_name['Block'] = _BLOCK 1391 | DESCRIPTOR.message_types_by_name['CompactBlock'] = _COMPACTBLOCK 1392 | DESCRIPTOR.message_types_by_name['BlockWithProof'] = _BLOCKWITHPROOF 1393 | DESCRIPTOR.message_types_by_name['BlockTxs'] = _BLOCKTXS 1394 | DESCRIPTOR.message_types_by_name['BlackList'] = _BLACKLIST 1395 | DESCRIPTOR.message_types_by_name['StateSignal'] = _STATESIGNAL 1396 | DESCRIPTOR.enum_types_by_name['ProofType'] = _PROOFTYPE 1397 | DESCRIPTOR.enum_types_by_name['Crypto'] = _CRYPTO 1398 | _sym_db.RegisterFileDescriptor(DESCRIPTOR) 1399 | 1400 | Proof = _reflection.GeneratedProtocolMessageType( 1401 | 'Proof', 1402 | (_message.Message, ), 1403 | dict( 1404 | DESCRIPTOR=_PROOF, 1405 | __module__='blockchain_pb2' 1406 | # @@protoc_insertion_point(class_scope:Proof) 1407 | )) 1408 | _sym_db.RegisterMessage(Proof) 1409 | 1410 | BlockHeader = _reflection.GeneratedProtocolMessageType( 1411 | 'BlockHeader', 1412 | (_message.Message, ), 1413 | dict( 1414 | DESCRIPTOR=_BLOCKHEADER, 1415 | __module__='blockchain_pb2' 1416 | # @@protoc_insertion_point(class_scope:BlockHeader) 1417 | )) 1418 | _sym_db.RegisterMessage(BlockHeader) 1419 | 1420 | Status = _reflection.GeneratedProtocolMessageType( 1421 | 'Status', 1422 | (_message.Message, ), 1423 | dict( 1424 | DESCRIPTOR=_STATUS, 1425 | __module__='blockchain_pb2' 1426 | # @@protoc_insertion_point(class_scope:Status) 1427 | )) 1428 | _sym_db.RegisterMessage(Status) 1429 | 1430 | AccountGasLimit = _reflection.GeneratedProtocolMessageType( 1431 | 'AccountGasLimit', 1432 | (_message.Message, ), 1433 | dict( 1434 | SpecificQuotaLimitEntry=_reflection.GeneratedProtocolMessageType( 1435 | 'SpecificQuotaLimitEntry', 1436 | (_message.Message, ), 1437 | dict( 1438 | DESCRIPTOR=_ACCOUNTGASLIMIT_SPECIFICQUOTALIMITENTRY, 1439 | __module__='blockchain_pb2' 1440 | # @@protoc_insertion_point(class_scope:AccountGasLimit.SpecificQuotaLimitEntry) 1441 | )), 1442 | DESCRIPTOR=_ACCOUNTGASLIMIT, 1443 | __module__='blockchain_pb2' 1444 | # @@protoc_insertion_point(class_scope:AccountGasLimit) 1445 | )) 1446 | _sym_db.RegisterMessage(AccountGasLimit) 1447 | _sym_db.RegisterMessage(AccountGasLimit.SpecificQuotaLimitEntry) 1448 | 1449 | RichStatus = _reflection.GeneratedProtocolMessageType( 1450 | 'RichStatus', 1451 | (_message.Message, ), 1452 | dict( 1453 | DESCRIPTOR=_RICHSTATUS, 1454 | __module__='blockchain_pb2' 1455 | # @@protoc_insertion_point(class_scope:RichStatus) 1456 | )) 1457 | _sym_db.RegisterMessage(RichStatus) 1458 | 1459 | Transaction = _reflection.GeneratedProtocolMessageType( 1460 | 'Transaction', 1461 | (_message.Message, ), 1462 | dict( 1463 | DESCRIPTOR=_TRANSACTION, 1464 | __module__='blockchain_pb2' 1465 | # @@protoc_insertion_point(class_scope:Transaction) 1466 | )) 1467 | _sym_db.RegisterMessage(Transaction) 1468 | 1469 | UnverifiedTransaction = _reflection.GeneratedProtocolMessageType( 1470 | 'UnverifiedTransaction', 1471 | (_message.Message, ), 1472 | dict( 1473 | DESCRIPTOR=_UNVERIFIEDTRANSACTION, 1474 | __module__='blockchain_pb2' 1475 | # @@protoc_insertion_point(class_scope:UnverifiedTransaction) 1476 | )) 1477 | _sym_db.RegisterMessage(UnverifiedTransaction) 1478 | 1479 | SignedTransaction = _reflection.GeneratedProtocolMessageType( 1480 | 'SignedTransaction', 1481 | (_message.Message, ), 1482 | dict( 1483 | DESCRIPTOR=_SIGNEDTRANSACTION, 1484 | __module__='blockchain_pb2' 1485 | # @@protoc_insertion_point(class_scope:SignedTransaction) 1486 | )) 1487 | _sym_db.RegisterMessage(SignedTransaction) 1488 | 1489 | BlockBody = _reflection.GeneratedProtocolMessageType( 1490 | 'BlockBody', 1491 | (_message.Message, ), 1492 | dict( 1493 | DESCRIPTOR=_BLOCKBODY, 1494 | __module__='blockchain_pb2' 1495 | # @@protoc_insertion_point(class_scope:BlockBody) 1496 | )) 1497 | _sym_db.RegisterMessage(BlockBody) 1498 | 1499 | CompactBlockBody = _reflection.GeneratedProtocolMessageType( 1500 | 'CompactBlockBody', 1501 | (_message.Message, ), 1502 | dict( 1503 | DESCRIPTOR=_COMPACTBLOCKBODY, 1504 | __module__='blockchain_pb2' 1505 | # @@protoc_insertion_point(class_scope:CompactBlockBody) 1506 | )) 1507 | _sym_db.RegisterMessage(CompactBlockBody) 1508 | 1509 | Block = _reflection.GeneratedProtocolMessageType( 1510 | 'Block', 1511 | (_message.Message, ), 1512 | dict( 1513 | DESCRIPTOR=_BLOCK, 1514 | __module__='blockchain_pb2' 1515 | # @@protoc_insertion_point(class_scope:Block) 1516 | )) 1517 | _sym_db.RegisterMessage(Block) 1518 | 1519 | CompactBlock = _reflection.GeneratedProtocolMessageType( 1520 | 'CompactBlock', 1521 | (_message.Message, ), 1522 | dict( 1523 | DESCRIPTOR=_COMPACTBLOCK, 1524 | __module__='blockchain_pb2' 1525 | # @@protoc_insertion_point(class_scope:CompactBlock) 1526 | )) 1527 | _sym_db.RegisterMessage(CompactBlock) 1528 | 1529 | BlockWithProof = _reflection.GeneratedProtocolMessageType( 1530 | 'BlockWithProof', 1531 | (_message.Message, ), 1532 | dict( 1533 | DESCRIPTOR=_BLOCKWITHPROOF, 1534 | __module__='blockchain_pb2' 1535 | # @@protoc_insertion_point(class_scope:BlockWithProof) 1536 | )) 1537 | _sym_db.RegisterMessage(BlockWithProof) 1538 | 1539 | BlockTxs = _reflection.GeneratedProtocolMessageType( 1540 | 'BlockTxs', 1541 | (_message.Message, ), 1542 | dict( 1543 | DESCRIPTOR=_BLOCKTXS, 1544 | __module__='blockchain_pb2' 1545 | # @@protoc_insertion_point(class_scope:BlockTxs) 1546 | )) 1547 | _sym_db.RegisterMessage(BlockTxs) 1548 | 1549 | BlackList = _reflection.GeneratedProtocolMessageType( 1550 | 'BlackList', 1551 | (_message.Message, ), 1552 | dict( 1553 | DESCRIPTOR=_BLACKLIST, 1554 | __module__='blockchain_pb2' 1555 | # @@protoc_insertion_point(class_scope:BlackList) 1556 | )) 1557 | _sym_db.RegisterMessage(BlackList) 1558 | 1559 | StateSignal = _reflection.GeneratedProtocolMessageType( 1560 | 'StateSignal', 1561 | (_message.Message, ), 1562 | dict( 1563 | DESCRIPTOR=_STATESIGNAL, 1564 | __module__='blockchain_pb2' 1565 | # @@protoc_insertion_point(class_scope:StateSignal) 1566 | )) 1567 | _sym_db.RegisterMessage(StateSignal) 1568 | 1569 | _ACCOUNTGASLIMIT_SPECIFICQUOTALIMITENTRY._options = None 1570 | # @@protoc_insertion_point(module_scope) 1571 | -------------------------------------------------------------------------------- /src/cita/make_tx.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, Tuple 2 | import random 3 | import string 4 | 5 | import sha3 6 | from ecdsa import SigningKey, SECP256k1 7 | from secp256k1 import PrivateKey 8 | 9 | from .blockchain_pb2 import Transaction, UnverifiedTransaction, Crypto 10 | from .util import param_to_str, param_to_bytes 11 | 12 | 13 | class SignerBase: 14 | def __init__(self, version: int = 2, chain_id: int = 1): 15 | if version not in (0, 1, 2): 16 | raise NotImplementedError(f'unexpected version {version}') 17 | self.version = version 18 | self.chain_id = chain_id 19 | 20 | def generate_account(self, private_key: bytes = b'') -> Tuple[str, str, str]: 21 | """ 22 | 生成账户的地址和密钥对. 23 | 24 | :param private_key: 私钥. 如果为空, 则重新生成, 否则从私钥还原. 25 | :return: (私钥, 公钥, 账户地址) 26 | """ 27 | raise NotImplementedError('virtual method') 28 | 29 | def make_raw_tx(self, private_key: bytes, receiver: bytes, bytecode: bytes, valid_until_block: int, value: int, quota: int) -> bytes: 30 | """ 31 | 对交易数据进行签名. 32 | 33 | :param private_key: 私钥. 34 | :param receiver: 接收方地址. 如果是合约部署, 则为b''. 35 | :param bytecode: 字节码. 36 | :param valid_until_block: 交易的最后期限. 默认为当前区块高度+88 37 | :param value: 金额. 38 | :param quota: 调用配额. 39 | :return: 签名后的bytes. 40 | """ 41 | raise NotImplementedError('virtual method') 42 | 43 | 44 | # 目前支持两种加密方法 secp256k1, ed25519 45 | class SignerSecp256k1(SignerBase): 46 | def __init__(self, version: int = 2, chain_id: int = 1): 47 | super().__init__(version, chain_id) 48 | 49 | def generate_account(self, private_key: bytes = b'') -> Tuple[str, str, str]: 50 | """ 51 | 生成账户的地址和密钥对. 52 | 53 | :param private_key: 私钥. 如果为空, 则重新生成, 否则从私钥还原. 54 | :return: (私钥, 公钥, 账户地址) 55 | """ 56 | keccak = sha3.keccak_256() 57 | if private_key == b'': 58 | priv = SigningKey.generate(curve=SECP256k1) 59 | else: 60 | priv = SigningKey.from_string(private_key, curve=SECP256k1) 61 | 62 | pub = priv.get_verifying_key().to_string() 63 | 64 | keccak.update(pub) 65 | address = '0x' + keccak.hexdigest()[24:] 66 | 67 | return param_to_str(priv.to_string()), param_to_str(pub), address 68 | 69 | def make_raw_tx(self, private_key: bytes, receiver: bytes, bytecode: bytes, valid_until_block: int, value: int, quota: int) -> bytes: 70 | """ 71 | 对交易数据进行签名. 72 | 73 | :param private_key: 私钥. 74 | :param receiver: 接收方地址. 如果是合约部署, 则为b''. 75 | :param bytecode: 字节码. 76 | :param valid_until_block: 交易的最后期限. 默认为当前区块高度+88 77 | :param value: 金额. 78 | :param quota: 调用配额. 79 | :return: 签名后的bytes. 80 | """ 81 | _, _, sender = self.generate_account(private_key) 82 | pri_key = PrivateKey(private_key) 83 | 84 | tx = Transaction() 85 | tx.valid_until_block = valid_until_block 86 | tx.nonce = get_nonce() 87 | tx.version = self.version 88 | 89 | if self.version == 0: 90 | tx.chain_id = self.chain_id 91 | else: 92 | tx.chain_id_v1 = self.chain_id.to_bytes(32, byteorder='big') 93 | 94 | if receiver: 95 | if self.version == 0: 96 | tx.to = receiver 97 | else: 98 | tx.to_v1 = receiver 99 | tx.data = bytecode 100 | tx.value = value.to_bytes(32, byteorder='big') 101 | tx.quota = quota 102 | 103 | keccak = sha3.keccak_256() 104 | keccak.update(tx.SerializeToString()) 105 | message = keccak.digest() 106 | 107 | sign_recover = pri_key.ecdsa_sign_recoverable(message, raw=True) 108 | sig = pri_key.ecdsa_recoverable_serialize(sign_recover) 109 | 110 | signature = param_to_str(sig[0] + bytes(bytearray([sig[1]]))) 111 | 112 | unverify_tx = UnverifiedTransaction() 113 | unverify_tx.transaction.CopyFrom(tx) 114 | unverify_tx.signature = param_to_bytes(signature) 115 | unverify_tx.crypto = Crypto.Value('DEFAULT') 116 | 117 | return unverify_tx.SerializeToString() 118 | 119 | 120 | def get_nonce(size=6): 121 | """Get a random string.""" 122 | return (''.join( 123 | random.choice(string.ascii_uppercase + string.digits) 124 | for _ in range(size))) 125 | 126 | 127 | def decode_unverified_transaction(data: bytes) -> Dict: 128 | """ 129 | 反序列化 ``UnverifiedTransaction`` . 130 | 131 | :param data: ``UnverifiedTransaction`` 的序列化数据. 132 | :return: 等价的JSON结构. 133 | """ 134 | utx = UnverifiedTransaction() 135 | utx.ParseFromString(data) 136 | tx = utx.transaction 137 | return { 138 | 'transaction': { 139 | 'chaid_id': tx.chain_id, 140 | 'chain_id_v1': param_to_str(tx.chain_id_v1), 141 | 'data': param_to_str(tx.data), 142 | 'nonce': tx.nonce, 143 | 'quota': tx.quota, 144 | 'to': tx.to, 145 | 'to_v1': param_to_str(tx.to_v1), 146 | 'valid_until_block': tx.valid_until_block, 147 | 'value': param_to_str(tx.value), 148 | 'version': tx.version, 149 | }, 150 | 'signature': param_to_str(utx.signature), 151 | 'crypto': utx.crypto, 152 | } 153 | -------------------------------------------------------------------------------- /src/cita/sdk.py: -------------------------------------------------------------------------------- 1 | from typing import Iterable, Dict, List, Tuple, Optional, Union, cast 2 | from dataclasses import dataclass 3 | import json 4 | import time 5 | import ast 6 | from pathlib import Path 7 | import random 8 | 9 | import requests 10 | import sha3 # type: ignore 11 | 12 | from .util import PARAM, DEFAULT_QUOTA, LATEST_VERSION, param_to_str, param_to_bytes, join_param, encode_param, decode_param 13 | from .make_tx import SignerSecp256k1, decode_unverified_transaction 14 | 15 | # CITA built-in contract address 16 | STORE_ABI_ADDR = '0xffffffffffffffffffffffffffffffffff010001' 17 | BATCH_TX_ADDR = '0xffffffffffffffffffffffffffffffffff02000e' 18 | BATCH_TX_CALL = '0x82cc3327' 19 | 20 | 21 | class CitaClient: 22 | """ 23 | cita-cli的封装. 24 | 25 | 注意成员函数的参数, 如果是Union[str, bytes] 和返回值的编码都使用bytes, 以避免是否要加0x的困惑 26 | """ 27 | def __init__(self, url: str, timeout: int = 10, call_mode: str = 'latest', crypto_method: str = 'secp256k1', version: int = LATEST_VERSION, chain_id: int = 1): 28 | """ 29 | 指定cita环境. 30 | 31 | :param url: cita后端服务的url 32 | :param call_mode: 调用时使用已确认区块 `latest` , 还是待确认区块 `pending` 33 | :param timeout: JSON RPC或cita-cli的调用超时时间, 单位秒 34 | :param crypto_method: 加密机制. 默认secp256k1 35 | :param version: 链的版本, 默认为 2 36 | :param chain_id: 链id, 默认为 1 37 | """ 38 | if call_mode not in ('latest', 'pending'): 39 | raise ValueError('call_mode must be `latest` or `pending`') 40 | 41 | self.url = url 42 | self.call_mode = call_mode 43 | self.timeout = timeout 44 | if crypto_method == 'secp256k1': 45 | self.signer = SignerSecp256k1(version, chain_id) 46 | else: 47 | raise NotImplementedError(crypto_method) 48 | 49 | def set_call_mode(self, mode): 50 | """ 51 | 设置调用只读方法时, 是使用已确认区块的数据, 还是待确认区块的数据. 52 | 53 | :param mode: 默认'latest', 使用已确认区块; 'pending', 使用待确认区块. 54 | """ 55 | if mode not in ('latest', 'pending'): 56 | raise ValueError('call_mode must be `latest` or `pending`') 57 | self.call_mode = mode 58 | 59 | def _jsonrpc(self, method: str, params: List) -> Union[None, str, Dict, List]: 60 | """ 61 | 执行jsonrpc调用. 62 | 63 | :param method: JSON RPC的方法名 64 | :param params: 被调方法的实参列表 65 | :return: JSON 66 | """ 67 | req_id = random.randint(1, 10000) 68 | req = { 69 | "jsonrpc": "2.0", 70 | "id": req_id, 71 | "method": method, 72 | "params": params 73 | } 74 | resp = requests.post(self.url, json=req, timeout=self.timeout) 75 | try: 76 | rj = resp.json() 77 | assert rj['id'] == req_id 78 | return rj['result'] 79 | except Exception: 80 | raise RuntimeError(f'`{method}` jsonrpc failed. code={resp.status_code} reason={resp.text} original_req={req}') 81 | 82 | def create_key(self) -> Dict[str, str]: 83 | """ 84 | 创建账户. 只有用私钥签名发起交易后, 才会改变链上状态. 85 | 86 | :return: 如 ``{'address': '0x11...', 'public': '0x22...', 'private': '0x33...'}`` 87 | """ 88 | r = self.signer.generate_account() 89 | return {'private': r[0], 'public': r[1], 'address': r[2]} 90 | 91 | def get_peer_count(self) -> int: 92 | """兄弟节点个数.""" 93 | r = self._jsonrpc('peerCount', []) 94 | r = cast(str, r) 95 | assert r.startswith('0x') 96 | return ast.literal_eval(r) 97 | 98 | def get_peers(self) -> Dict[str, str]: 99 | """ 100 | 获取兄弟节点信息. 101 | 102 | :return: 各个节点的信息, {节点名: 节点ip, ...} 103 | """ 104 | r = self._jsonrpc('peersInfo', []) 105 | r = cast(Dict, r) 106 | return r.get('peers', {}) 107 | 108 | def get_latest_block_number(self) -> int: 109 | """最新区块的高度.""" 110 | r = self._jsonrpc('blockNumber', []) 111 | r = cast(str, r) 112 | assert r.startswith('0x') 113 | return ast.literal_eval(r) 114 | 115 | def get_block_by_hash(self, hash: PARAM, tx_detail: bool = False) -> Dict: 116 | """ 117 | 根据区块hash获取区块详情. 118 | 119 | :param hash: 32字节的hash 120 | :param tx_detail: True 区块中会包含交易详情, 否则只包含交易hash 121 | :return: 区块详情 122 | """ 123 | hash_str = param_to_str(hash) 124 | assert len(hash_str) == 64 + 2 125 | r = self._jsonrpc('getBlockByHash', [hash_str, tx_detail]) 126 | return cast(Dict, r) 127 | 128 | def get_block_by_number(self, height: int, tx_detail: bool = False) -> Dict: 129 | """ 130 | 根据区块id获取区块详情. 131 | 132 | :param height: 区块高度, 从0起 133 | :param tx_detail: True 区块中会包含交易详情, 否则只包含交易hash 134 | :return: 区块详情 135 | """ 136 | assert height >= 0 137 | r = self._jsonrpc('getBlockByNumber', ['0x%02x' % height, tx_detail]) 138 | return cast(Dict, r) 139 | 140 | def get_meta_data(self) -> Dict: 141 | """ 142 | 查询链上元数据. 143 | """ 144 | r = self._jsonrpc('getMetaData', [self.call_mode]) 145 | return cast(Dict, r) 146 | 147 | def send_raw_transaction(self, data: PARAM) -> str: 148 | """ 149 | 发送原始交易数据. 150 | 151 | :param data: 待发送的数据. 152 | :return: 交易hash. 153 | """ 154 | r = self._jsonrpc('sendRawTransaction', [param_to_str(data)]) 155 | return cast(Dict, r)['hash'] 156 | 157 | def send_transaction(self, private_key: PARAM, to_addr: PARAM, code: PARAM, value: int = 0, quota: int = DEFAULT_QUOTA, max_wait_block: int = 88) -> str: 158 | """ 159 | 发送完整交易数据. 160 | 161 | :param private_key: 私钥. 162 | :param to_addr: 接收方地址. 如果是合约部署, 则为b''. 163 | :param code: 字节码. 164 | :param value: 金额. 165 | :param quota: 调用配额. 166 | :param max_wait_block: 交易至多等待多少个区块. 默认88. 167 | :return: 交易hash. 168 | """ 169 | block_number = self.get_latest_block_number() 170 | data = self.signer.make_raw_tx(param_to_bytes(private_key), 171 | param_to_bytes(to_addr), 172 | param_to_bytes(code), 173 | block_number + max_wait_block, value, quota) 174 | return self.send_raw_transaction(data) 175 | 176 | def deploy_contract(self, private_key: PARAM, code: PARAM, param: PARAM = b'') -> str: 177 | """ 178 | 部署合约. 179 | 180 | :param private_key: 私钥 181 | :param code: 编译后的合约 182 | :param params: 合约构造函数的参数经encode_param编码后的bytes. 无参数用 b'' 183 | :return: 交易hash. 184 | """ 185 | return self.send_transaction(private_key, b'', param_to_bytes(join_param(code, param))) 186 | 187 | def confirm_transaction(self, tx_hash: PARAM, timeout: int = -1) -> Dict: 188 | """ 189 | 等待交易完成. 190 | 191 | :param tx_hash: 交易hash. 192 | :param timeout: 等待回执的时间, 单位秒. -1: 一直等待回执; 0: 无论是否达成共识, 直接返回; 其他值表示超时时间 193 | :return: 回执结果. 194 | """ 195 | r: Dict = {} 196 | t0 = time.time() 197 | r = self.get_transaction_receipt(tx_hash, 0) 198 | 199 | # 先获得交易回执 200 | while 'blockNumber' not in r: 201 | time.sleep(1) 202 | t1 = time.time() 203 | if t1 - t0 >= timeout != -1: 204 | raise RuntimeError('timeout') 205 | r = self.get_transaction_receipt(tx_hash, 0) 206 | 207 | this_block = ast.literal_eval(r['blockNumber']) 208 | while self.get_latest_block_number() - this_block < 1: # cita是先共识交易顺序, 后执行交易, 下个块公布上次答案, 所以会差1个块. 209 | time.sleep(1) 210 | t1 = time.time() 211 | if t1 - t0 >= timeout and timeout != -1: 212 | raise RuntimeError('timeout') 213 | return r 214 | 215 | def get_transaction_receipt(self, tx_hash: PARAM, timeout: int = -1) -> Dict: 216 | """ 217 | 查看回执结果. 218 | 219 | :param tx_hash: 交易hash. 220 | :param timeout: 等待回执的时间, 单位秒. -1: 一直等待回执; 0: 无论有无回执, 直接返回; 其他值表示超时时间 221 | :return: 回执结果. 如果交易还没执行且timeout=0, 则返回{}. 否则表示在pending区块中已经加入此交易, 期待共识 222 | """ 223 | t0 = time.time() 224 | 225 | while 1: 226 | r = self._jsonrpc('getTransactionReceipt', [param_to_str(tx_hash)]) 227 | if r is None: 228 | r = {} 229 | assert isinstance(r, dict) 230 | if timeout == 0 or r: 231 | break 232 | 233 | t1 = time.time() 234 | if t1 - t0 >= timeout and timeout != -1: 235 | raise RuntimeError('timeout') 236 | time.sleep(1) 237 | 238 | error = r.get('errorMessage') 239 | if error: # 交易失败 240 | raise RuntimeError(error) 241 | 242 | return r 243 | 244 | def call_readonly_func(self, contract_addr: PARAM, func_addr: PARAM, param: PARAM = b'', from_addr: PARAM = b'') -> bytes: 245 | """ 246 | 调用合约的只读函数. 247 | 248 | :param contract_addr: 合约地址, 20字节 249 | :param func_addr: 合约内的函数地址, 4字节 250 | :param param: 合约构造函数的参数经encode_param编码后的bytes. 无参数用 b'' 251 | :param from_addr: 调用者的地址, 默认是 b'' 252 | :return: 返回值编码的bytes 253 | """ 254 | # 构造 CallRequest 255 | to_ = param_to_str(contract_addr) 256 | assert len(to_) == 40 + 2 257 | req = {'to': to_} 258 | 259 | if from_addr: 260 | from_ = param_to_str(from_addr) 261 | assert len(from_) == 40 + 2 262 | req['from'] = from_ 263 | 264 | data = param_to_str(func_addr) 265 | assert len(data) == 8 + 2 266 | if param: 267 | data += param_to_str(param)[2:] 268 | req['data'] = data 269 | 270 | r = self._jsonrpc('call', [req, self.call_mode]) 271 | assert isinstance(r, str) and r.startswith('0x') 272 | return param_to_bytes(r) 273 | 274 | def call_func(self, private_key: PARAM, contract_addr: PARAM, func_addr: PARAM, param: PARAM = b'', quota: int = DEFAULT_QUOTA) -> str: 275 | """ 276 | 调用合约的函数. 277 | 278 | :param private_key: 私钥 279 | :param contract_addr: 合约地址 280 | :param func_addr: 合约内的函数地址 281 | :param param: 编码后的函数参数列表 282 | """ 283 | tx_hash = self.send_transaction(private_key, contract_addr, param_to_bytes(join_param(func_addr, param)), quota=quota) 284 | return tx_hash 285 | 286 | def batch_call_func(self, private_key: PARAM, tx_code_list: List[PARAM], quota: int = DEFAULT_QUOTA) -> str: 287 | """ 288 | 发起批量交易. 289 | 290 | :param private_key: 私钥. 291 | :param tx_code_list: 由ContractClass.get_tx_code生成的交易数据. 292 | :return: 交易hash 293 | """ 294 | data: List[bytes] = [] 295 | for tx_code in tx_code_list: 296 | c = param_to_bytes(tx_code) 297 | assert len(c) >= 20, 'bad tx_code' 298 | head, tail = c[:20], c[20:] 299 | n = len(tail) 300 | data += [head, n.to_bytes(4, byteorder='big'), tail] 301 | 302 | # 调用 BatchTx 合约的 multiTxs 方法. 303 | tx_hash = self.call_func(private_key, 304 | BATCH_TX_ADDR, 305 | BATCH_TX_CALL, 306 | encode_param('bytes', b''.join(data)), 307 | quota=quota) 308 | return tx_hash 309 | 310 | # def estimate_quota(self, contract_addr: PARAM, func_addr: PARAM, param: PARAM = b'', from_addr: PARAM = b'') -> int: 311 | # """ 312 | # 估计合约调用所需的quota. (只在商业版可用) 313 | 314 | # :param contract_addr: 合约地址. 315 | # :param func_addr: 合约内的函数地址. 316 | # :param param: 编码后的函数参数列表. 317 | # :param from_addr: 合约的调用方地址. 318 | # :return: 此调用所需的quota. 319 | # """ 320 | # to_ = param_to_str(contract_addr) 321 | # assert len(to_) == 40 + 2 322 | # req = {'to': to_} 323 | 324 | # if from_addr: 325 | # from_ = param_to_str(from_addr) 326 | # assert len(from_) == 40 + 2 327 | # req['from'] = from_ 328 | 329 | # data = param_to_str(func_addr) 330 | # assert len(data) == 8 + 2 331 | # if param: 332 | # data += param_to_str(param)[2:] 333 | # req['data'] = data 334 | 335 | # r = self._jsonrpc('estimateQuota', [req, self.call_mode]) 336 | # assert isinstance(r, str) and r.startswith('0x') 337 | # return ast.literal_eval(r) 338 | 339 | def get_code(self, contract_addr: PARAM) -> bytes: 340 | """ 341 | 获取合约代码. 342 | 343 | :param contract_addr: 合约地址, 20字节 344 | :return: 合约代码bytes 345 | """ 346 | addr = param_to_str(contract_addr) 347 | assert len(addr) == 42 348 | r = self._jsonrpc('getCode', [addr, self.call_mode]) 349 | assert isinstance(r, str) and r.startswith('0x') 350 | if r == '0x': # 合约不存在 351 | return b'' 352 | rb = param_to_bytes(r) 353 | return rb 354 | 355 | def get_abi(self, contract_addr: PARAM) -> List: 356 | """ 357 | 获取合约的ABI. 358 | 359 | :param contract_addr: 合约地址, 20字节 360 | :return: ABI的json 361 | """ 362 | addr = param_to_str(contract_addr) 363 | assert len(addr) == 42 364 | r = self._jsonrpc('getAbi', [addr, self.call_mode]) 365 | assert isinstance(r, str) and r.startswith('0x') 366 | if r == '0x': # 合约不存在或未绑定ABI 367 | return [] 368 | 369 | rb = param_to_bytes(r) 370 | rbs = decode_param('string', rb) 371 | return json.loads(rbs) 372 | 373 | def store_abi(self, private_key: PARAM, contract_addr: PARAM, abi: str) -> str: 374 | """ 375 | 将ABI追加给指定的合约. 376 | 377 | :param private_key: 私钥 378 | :param contract_addr: 合约地址 379 | :param abi: ABI的json的字符串形式 380 | :return: 交易hash 381 | """ 382 | abi_data = encode_param('string', abi) 383 | return self.send_transaction(private_key, STORE_ABI_ADDR, join_param(contract_addr, abi_data)) 384 | 385 | def get_transaction(self, tx_hash: PARAM) -> Dict: 386 | """ 387 | 获取交易详情. 388 | 389 | :param tx_hash: 交易hash, 32字节 390 | :return: JSON结构的交易详情 391 | """ 392 | h = param_to_str(tx_hash) 393 | assert len(h) == 64 + 2 394 | r = self._jsonrpc('getTransaction', [h]) 395 | if not r: 396 | return {} 397 | return cast(Dict, r) 398 | 399 | def get_transaction_count(self, addr: PARAM) -> int: 400 | """ 401 | 获取指定账户发起的交易数量. 402 | 403 | :param addr: 账户地址, 20字节 404 | :return: 交易数量 405 | """ 406 | addr_ = param_to_str(addr) 407 | assert len(addr) == 40 + 2 408 | 409 | r = self._jsonrpc('getTransactionCount', [addr_, self.call_mode]) 410 | if not r or r == '0x': 411 | return 0 412 | assert isinstance(r, str) 413 | return ast.literal_eval(r) 414 | 415 | # def decode_transaction_content(self, content: PARAM) -> Dict: 416 | # """ 417 | # 把交易内容解析成结构化的各个字段. 418 | 419 | # :param content: 交易内容, 就是 JSON RPC ``getTransaction`` 返回的``content``字段. 420 | # """ 421 | # cmd = ['tx', 'decode-unverifiedTransaction', 422 | # '--content', param_to_str(content)] 423 | # return json.loads(self._raw_cmd(cmd)) 424 | 425 | def decode_transaction_content(self, content: PARAM) -> Dict: 426 | """ 427 | 把交易内容解析成结构化的各个字段. 428 | 429 | :param content: 交易内容, 就是 JSON RPC ``getTransaction`` 返回的``content``字段. 430 | """ 431 | # TODO: 找到decode_transaction_content的对应物. 432 | return decode_unverified_transaction(param_to_bytes(content)) 433 | 434 | 435 | @dataclass 436 | class ABI: 437 | func_name: str # 合约方法名 438 | func_addr: str # 合约方法地址 0x12345678 439 | param_types: str # 参数 440 | return_types: str # 返回值 441 | mutable: bool # 是否只读 442 | quota: int # 调用配额 443 | 444 | 445 | class ContractClass: 446 | 447 | def __init__(self, sol_file: Path, client: CitaClient, func_name2quota: Optional[Dict[str, int]] = None): 448 | """ 449 | 生成ABI定义好的合约调用对象. 450 | 451 | :param sol_file: ``.sol`` 合约文件路径. 此文件中应该包含两行注释: 452 | 453 | - ``BYTECODE=6080bc...`` , **不** 包含0x开头的合约二进制的hex编码串 454 | - ``ABI=[{"inputs": ...}]`` , ABI的JSON定义 455 | 456 | :param client: CitaClient. 457 | :param func_name2quota: 方法名->最大Quota. 458 | """ 459 | self.client = client 460 | self.name, self.bytecode, self.abi = self._parse_sol_file(sol_file) 461 | self.func_mapping: Dict[str, ABI] = self._parse_abi(self.abi, func_name2quota if func_name2quota else {}) 462 | 463 | def get_raw_abi(self) -> str: 464 | """返回remix提供的原始abi.""" 465 | return self.abi 466 | 467 | def get_code(self) -> str: 468 | """获取合约的bytecode.""" 469 | return self.bytecode 470 | 471 | @staticmethod 472 | def _parse_sol_file(sol_file: Path) -> Tuple[str, str, str]: 473 | """解析.sol, 提取主合约名, BYTECODE, ABI.""" 474 | assert sol_file.name.endswith('.sol'), '请输入合约定义文件的路径 *.sol' 475 | classname = sol_file.stem 476 | fn = sol_file.with_suffix('.bin') 477 | with open(fn) as f: 478 | lines = [i.strip() for i in f] 479 | 480 | bytecode = '' 481 | for no, line in enumerate(lines): 482 | # 找出主合约的部分 ======= :XXX ======= 483 | if line.find(':' + classname) != -1: 484 | assert lines[no + 1].strip() == 'Binary:' 485 | assert lines[no + 3].strip() == 'Contract JSON ABI' 486 | bytecode = '0x' + lines[no + 2] 487 | abi = lines[no + 4] 488 | break 489 | 490 | if not bytecode: 491 | raise RuntimeError(f'找不到合约编译后的内容: {fn}') 492 | 493 | return classname, bytecode, abi 494 | 495 | @staticmethod 496 | def _parse_abi(abi: str, func_name2quota: Dict[str, int]) -> Dict[str, ABI]: 497 | """解析ABI到函数签名.""" 498 | result: Dict[str, ABI] = {} 499 | for func_def in json.loads(abi): 500 | if func_def['type'] not in ('function', 'constructor'): # 比如 event 501 | continue 502 | func_name = func_def.get('name', '') 503 | return_types = ','.join(i['type'] for i in func_def.get('outputs', [])) 504 | mutable = func_def['stateMutability'] not in ('view', 'pure', 'constant') 505 | param_types = ','.join(i['type'] for i in func_def['inputs']) 506 | sig = f'{func_name}({param_types})' 507 | func_addr = '0x' + sha3.keccak_256(sig.encode()).hexdigest()[:8] 508 | t = ABI(func_name, func_addr, param_types, return_types, mutable, 509 | func_name2quota.get(func_name, DEFAULT_QUOTA)) 510 | 511 | # 重载函数只支持第一个名字的自动映射, 其他都要使用方法地址. 512 | if func_name not in result: 513 | result[func_name] = t 514 | if func_name != '': # 跳过构造函数 515 | result[func_addr] = t 516 | return result 517 | 518 | def instantiate_raw(self, private_key: PARAM, *args) -> str: 519 | """ 520 | 部署合约, 不等待交易回执. 521 | 522 | :param private_key: 用于部署合约的私钥. 523 | :param args: 合约构造函数的参数. 524 | :return: 部署交易hash. 525 | """ 526 | if args == (): 527 | param = b'' 528 | else: 529 | param = encode_param(self.func_mapping[''].param_types, args) 530 | return self.client.deploy_contract(private_key, self.bytecode, param) 531 | 532 | def instantiate(self, private_key: PARAM, *args) -> Tuple['ContractProxy', str, str]: 533 | """ 534 | 部署合约, 等待交易回执. 535 | 536 | :param private_key: 用于部署合约的私钥 537 | :param args: 合约构造函数的参数. 538 | :return: (合约实例的封装, 合约地址, 部署交易hash) 539 | """ 540 | tx_hash = self.instantiate_raw(private_key, *args) 541 | r = self.client.get_transaction_receipt(tx_hash) 542 | contract_addr = r['contractAddress'] 543 | # if store_abi: 544 | # h = client.store_abi(private_key, contract_addr, json.dumps(self.abi, separators=(',', ':'))) 545 | proxy = self.bind(contract_addr, private_key) 546 | return proxy, contract_addr, tx_hash 547 | 548 | def batch_instantiate(self, private_key: PARAM, param_list: Iterable) -> List[Tuple['ContractProxy', str, str]]: 549 | """ 550 | 批量的部署合约, 等待交易回执. 551 | 552 | :param private_key: 用于部署合约的私钥 553 | :param param_list: 每个合约构造函数的参数 554 | :return: (合约实例的封装, 合约地址, 部署交易hash) 555 | """ 556 | result: List[Tuple['ContractProxy', str, str]] = [] 557 | 558 | tx_hash_list = [self.instantiate_raw(private_key, *args if isinstance(args, tuple) else (args,)) for args in param_list] 559 | 560 | for tx_hash in tx_hash_list: 561 | r = self.client.get_transaction_receipt(tx_hash, self.client.timeout) 562 | contract_addr = r['contractAddress'] 563 | proxy = self.bind(contract_addr, private_key) 564 | result.append((proxy, contract_addr, tx_hash)) 565 | return result 566 | 567 | def bind(self, contract_addr: PARAM, private_key: PARAM) -> 'ContractProxy': 568 | """ 569 | 绑定到一个以部署的合约地址. 570 | 571 | :param private_key: 用于部署合约的私钥 572 | :return: 合约实例的封装 573 | """ 574 | return ContractProxy(self.name, self.func_mapping, self.client, private_key, contract_addr) 575 | 576 | 577 | class ContractProxy: 578 | """合约对象的代理, 用于转发函数调用. 通过proxy._ContractProxy__contract_addr可以获得合约地址.""" 579 | 580 | def __init__(self, class_name: str, func_mapping: Dict[str, ABI], client: CitaClient, private_key: PARAM, contract_addr: PARAM): 581 | """ 582 | 初始化. 583 | 584 | :param class_name: 合约名称 585 | :param func_mapping: func_name -> (func_name, func_addr, param_types, return_types, quota) 586 | :param client: CitaClient对象 587 | :param private_key: 私钥 588 | :param contract_addr: 合约部署地址, 20字节 589 | """ 590 | # 注意. 使用特殊的成员变量命名方式, 尽力避免与合约方法的冲突 591 | self.class_name__ = class_name 592 | self.func_mapping__ = func_mapping.copy() 593 | self.client__ = client 594 | self.private_key__ = private_key 595 | self.contract_addr__ = contract_addr 596 | 597 | def do_call_func__(self, func_addr: str, args): 598 | """ 599 | 执行合约方法调用. 600 | 601 | :param func_addr: 合约方法地址. 602 | :param args: 参数, 需配合合约方法的 param_type. 603 | :return: 对普通方法返回tx_hash, 对只读方法返回解码后的返回值. 604 | """ 605 | abi = self.func_mapping__[func_addr] 606 | 607 | if not args: 608 | arg_bytes = b'' 609 | else: 610 | arg_bytes = encode_param(abi.param_types, args) 611 | 612 | if abi.mutable: # 普通方法调用, 返回回执哈希 613 | return self.client__.call_func(self.private_key__, self.contract_addr__, func_addr, param=arg_bytes, quota=abi.quota) 614 | 615 | # 只读方法调用, 返回结果 616 | return_bytes = self.client__.call_readonly_func(self.contract_addr__, func_addr, param=arg_bytes) 617 | return decode_param(abi.return_types, return_bytes) 618 | 619 | def get_tx_code(self, func_name_or_addr: str, args=()) -> str: 620 | """ 621 | 计算调用合约方法时的tx_code. 往往用于批量调用. 622 | 623 | :param func_name_or_addr: 合约方法地址. 624 | :param args: 参数, 需配合合约方法的 param_type. 625 | :return: '0x'开头的字符串, 由(合约地址 + 方法地址 + 编码后的参数)拼接而成. 626 | """ 627 | abi = self.func_mapping__[func_name_or_addr] 628 | if args == (): 629 | arg_bytes = b'' 630 | else: 631 | arg_bytes = encode_param(abi.param_types, args) 632 | return join_param(self.contract_addr__, abi.func_addr, arg_bytes) 633 | 634 | def __getattr__(self, func_name_or_addr: str) -> "Functor": 635 | """ 636 | 选中一个合约方法. (仅在找不到名字时才会进入此函数) 637 | 638 | :param func_name_or_addr: 合约方法名或方法地址. 639 | :return: Functor 640 | """ 641 | if func_name_or_addr.startswith('__'): # 请求未实现的内部属性或方法 642 | raise AttributeError(func_name_or_addr) 643 | return self[func_name_or_addr] 644 | 645 | def __getitem__(self, func_name_or_addr: str) -> "Functor": 646 | """ 647 | contract_obj['xxx'] 选中一个合约函数. 648 | 649 | :param func_name_or_addr: 合约方法名或方法地址. 650 | :return: Functor 651 | """ 652 | abi = self.func_mapping__.get(func_name_or_addr) 653 | if abi is None: 654 | raise KeyError(f'function `{func_name_or_addr}` is not registered in Contract: `{self.class_name__}`') 655 | return Functor(self, abi.func_addr) 656 | 657 | 658 | class Functor: 659 | """合约函数的封装.""" 660 | 661 | def __init__(self, proxy: ContractProxy, func_addr: str): 662 | """ 663 | 初始化. 664 | 665 | :param proxy: ContractProxy对象 666 | :param func_addr: 合约函数地址 667 | """ 668 | self.proxy = proxy 669 | self.func_addr = func_addr 670 | 671 | def __call__(self, *args): 672 | """ 673 | 发起合约调用. 674 | 675 | :param argument: 实际参数, 注意必须把所有参数都放在tuple中, 比如('abc', 1) 676 | :return: 如果是普通函数调用, 返回交易回执, '0x...'. 如果是只读函数调用, 返回解码后的python结果 677 | """ 678 | return self.proxy.do_call_func__(self.func_addr, args) 679 | 680 | @property 681 | def name(self) -> str: 682 | return self.proxy.func_mapping__[self.func_addr].func_name 683 | 684 | @property 685 | def address(self) -> str: 686 | return self.func_addr 687 | 688 | @property 689 | def param_types(self) -> str: 690 | return self.proxy.func_mapping__[self.func_addr].param_types 691 | 692 | @property 693 | def return_types(self) -> str: 694 | return self.proxy.func_mapping__[self.func_addr].return_types 695 | 696 | @property 697 | def mutable(self) -> bool: 698 | return self.proxy.func_mapping__[self.func_addr].mutable 699 | 700 | @property 701 | def quota(self) -> int: 702 | return self.proxy.func_mapping__[self.func_addr].quota 703 | 704 | @quota.setter 705 | def quota(self, new_quota: int): 706 | self.proxy.func_mapping__[self.func_addr].quota = new_quota 707 | -------------------------------------------------------------------------------- /src/cita/util.py: -------------------------------------------------------------------------------- 1 | """ 2 | 定义一些基础工具. 3 | """ 4 | from typing import Union, List 5 | from binascii import hexlify, unhexlify 6 | from eth_abi import encode_single, decode_single 7 | 8 | 9 | PARAM = Union[str, bytes] # 用于CitaClient方法的参数类型. 如果是str, 默认都具有'0x'前缀 10 | DEFAULT_QUOTA = 10000000 # 默认调用每个合约方法所消耗的quota上限. 11 | LATEST_VERSION = 2 # 默认的区块链版本号. 12 | 13 | 14 | def param_to_str(p: PARAM) -> str: 15 | """将PARAM统一到str形式.""" 16 | assert (isinstance(p, str) and p.startswith('0x')) or isinstance(p, bytes) 17 | return p if isinstance(p, str) else f'0x{hexlify(p).decode()}' 18 | 19 | 20 | def param_to_bytes(p: PARAM) -> bytes: 21 | """将PARAM统一到bytes形式.""" 22 | assert (isinstance(p, str) and p.startswith('0x')) or isinstance(p, bytes) 23 | return p if isinstance(p, bytes) else unhexlify(p[2:]) 24 | 25 | 26 | def join_param(*param_list) -> str: 27 | """ 28 | 用于拼接多个PARAM类型. 29 | 30 | :param param_list: 参数列表. 如果某个元素是str, 则开头必须是'0x' 31 | :return: 拼接后的字符串 32 | """ 33 | if not param_list: 34 | return '' 35 | 36 | # 参数中有str, 返回str类型 37 | ret: List[str] = ['0x'] 38 | for i in param_list: 39 | if isinstance(i, bytes): 40 | ret.append(hexlify(i).decode()) 41 | elif isinstance(i, str): 42 | assert i[:2] == '0x' 43 | ret.append(i[2:]) 44 | else: 45 | assert 0 46 | return ''.join(ret) 47 | 48 | 49 | def equal_param(lhs: PARAM, rhs: PARAM) -> bool: 50 | """判断两个参数的内容是否相同.""" 51 | if type(lhs) is type(rhs): 52 | return lhs == rhs 53 | return param_to_bytes(lhs) == param_to_bytes(rhs) 54 | 55 | 56 | def encode_param(types: str, values) -> bytes: 57 | r""" 58 | 返回参数编码后的字符串. 59 | 60 | :param types, values: 参考eth_abi.encode_single的文档. 61 | :return: 如b'\x0b\xad\xf0\x0d'... 62 | """ 63 | if not types: 64 | return b'' 65 | 66 | if types[0] != '(': 67 | types = f'({types})' 68 | 69 | if isinstance(values, tuple): 70 | return encode_single(types, values) 71 | else: 72 | return encode_single(types, (values,)) 73 | 74 | 75 | def decode_param(types: str, bin: bytes): 76 | """ 77 | 返回解码后的python类型的数据. 78 | 79 | :param types, values: 参考eth_abi.decode_single的文档 80 | :return: python类型的数据. 如果包含2个以上的值, 返回Tuple. 如果只有一个值, 则解开Tuple 81 | """ 82 | if (not types) or types[0] != '(': 83 | types = f'({types})' 84 | ret = decode_single(types, bin) 85 | if len(ret) == 0: 86 | return () 87 | return ret if len(ret) >= 2 else ret[0] 88 | -------------------------------------------------------------------------------- /tests/DoubleStorage.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | contract Dummy { // forward declaration 4 | string dummy; 5 | } 6 | 7 | contract DoubleStorage { 8 | uint public x; 9 | uint public y; 10 | 11 | constructor(uint _x, uint _y) public { 12 | x = _x; 13 | y = _y; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/Dummy.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | contract Dummy { 4 | } 5 | -------------------------------------------------------------------------------- /tests/SimpleStorage.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | contract Dummy { // forward declaration 4 | string dummy; 5 | } 6 | 7 | contract SimpleStorage { 8 | uint x; 9 | 10 | event Added(uint x); 11 | 12 | constructor(uint _x) public { 13 | x = _x; 14 | } 15 | 16 | function set(uint _x) public { 17 | x = _x; 18 | } 19 | 20 | function reset() public { 21 | x = 0; 22 | } 23 | 24 | function add(uint _x) public returns (uint) { 25 | emit Added(_x); 26 | x += _x; 27 | return x; 28 | } 29 | 30 | function add_by_vec(uint[] a, uint[] b) public returns (uint) { 31 | require(a.length == b.length); 32 | for (uint i = 0; i < a.length; i++) { 33 | x += a[i] * b[i]; 34 | } 35 | return x; 36 | } 37 | 38 | // 只读方法. 39 | function get() public view returns (uint) { 40 | return x; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/test_sdk.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """测试CitaClient.""" 4 | from pathlib import Path 5 | import json 6 | import pytest 7 | 8 | from cita import CitaClient, ContractClass 9 | from cita import equal_param, encode_param, decode_param, join_param, param_to_bytes, param_to_str 10 | 11 | 12 | # Change to your own cita rpc endpoint. 13 | CITA_URL = 'http://127.0.0.1:1337' 14 | client = CitaClient(CITA_URL) 15 | 16 | 17 | def test_create_key(): 18 | r = client.create_key() 19 | assert len(r['address'][2:]) == 20 * 2 20 | assert len(r['public'][2:]) == 64 * 2 21 | assert len(r['private'][2:]) == 32 * 2 22 | 23 | 24 | def test_encode_decode(): 25 | r = encode_param('string', 'abc') 26 | ans = '0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000036162630000000000000000000000000000000000000000000000000000000000' 27 | assert param_to_str(r) == ans 28 | assert param_to_bytes(ans) == r 29 | assert equal_param(r, ans) is True 30 | assert decode_param('string', r) == 'abc' 31 | 32 | r = encode_param('(int,bool)', (-1, True)) 33 | ans = '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000001' 34 | assert equal_param(r, ans) is True 35 | assert decode_param('(int,bool)', r) == (-1, True) 36 | 37 | r = encode_param('int', 1) 38 | ans = '0x0000000000000000000000000000000000000000000000000000000000000001' 39 | assert equal_param(r, ans) is True 40 | assert decode_param('int', r) == 1 41 | 42 | ans = '0x0badf00d' 43 | assert equal_param(b'\x0b\xad\xf0\x0d', '0x0badf00d') is True 44 | assert join_param(b'\x0b\xad', '0xf00d') == '0x0badf00d' 45 | assert join_param(b'\x0b\xad', b'\xf0\x0d') == ans 46 | 47 | r = encode_param('', '') 48 | assert r == b'' 49 | 50 | assert equal_param(b'\x00', b'\x00') is True 51 | assert encode_param('', ()) == b'' 52 | assert decode_param('', b'') == () 53 | assert join_param() == '' 54 | 55 | 56 | def test_block_number(): 57 | assert client.get_latest_block_number() > 0 58 | 59 | 60 | def test_peer_count(): 61 | assert client.get_peer_count() >= 0 62 | 63 | 64 | def test_peers(): 65 | assert isinstance(client.get_peers(), dict) 66 | 67 | 68 | def test_get_block(): 69 | block = client.get_block_by_number(0) 70 | assert block['header']['number'] == '0x0' 71 | h = block['hash'] 72 | 73 | block2 = client.get_block_by_hash(h) 74 | assert block == block2 75 | 76 | 77 | def test_get_meta_data(): 78 | r = client.get_meta_data() 79 | assert 'version' in r 80 | 81 | 82 | def test_get_transaction_receipt_bad(): 83 | # 读取不存在的交易 84 | assert client.get_transaction_receipt(b'\x00' * 32, 0) == {} 85 | with pytest.raises(RuntimeError, match='timeout'): 86 | client.get_transaction_receipt(b'\x00' * 32, 1) 87 | 88 | with pytest.raises(RuntimeError, match='jsonrpc failed'): 89 | client.get_transaction_receipt(b'\x00') 90 | 91 | 92 | def test_get_abi_bad(): 93 | # 不存在的地址 94 | r = client.get_abi(b'\x00' * 20) 95 | assert r == [] 96 | r = client.get_code(b'\x00' * 20) 97 | assert r == b'' 98 | 99 | 100 | def test_decode_transaction_content(): 101 | r = client.decode_transaction_content('0x0aae03122032626131393465636637633034323333623862383764346633353938343061311880ade2042097ce0d2aa4024b2173f53863643239306334326138383430303361396234643566613263313665346138000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a1789c8bae562ac9cc4d55b25232323032d03530d4353650d2514a2bcacf058abdd8d6fa7cd7f2a7adcdcf364c313035303bb4fd65d364a0205004a82825b53819bfa2a7fd4d4f76f43ddfd4f964c7dca7b317bd689cf27442c7d309139fed9cf6b467fad3b5739fb5cc7fba03a866edd3fdcd4f7b76bf9cb9e859ef84175b173c6e986064f2b861e2931dab9e752d793e7bddcba973946a7586926363010adec36d000000000000000000000000000000000000000000000000000000000000003220000000000000000000000000000000000000000000000000000000000000000040024a14a26afce7d4bf0308b38970b1dadf6adfea2c7501522000000000000000000000000000000000000000000000000000000000000000011241f098ce3832fc5bd467083119292ec1a7e2239784f7734204fdf5e01d84a4e63c2b37cb4a05ef919c5ada1db6775c86dadacaf69f53fd54f09efb326d6b658e0601') 102 | assert r['transaction']['nonce'] == '2ba194ecf7c04233b8b87d4f359840a1' 103 | # r2 = client.decode_transaction_content2('0x0aae03122032626131393465636637633034323333623862383764346633353938343061311880ade2042097ce0d2aa4024b2173f53863643239306334326138383430303361396234643566613263313665346138000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a1789c8bae562ac9cc4d55b25232323032d03530d4353650d2514a2bcacf058abdd8d6fa7cd7f2a7adcdcf364c313035303bb4fd65d364a0205004a82825b53819bfa2a7fd4d4f76f43ddfd4f964c7dca7b317bd689cf27442c7d309139fed9cf6b467fad3b5739fb5cc7fba03a866edd3fdcd4f7b76bf9cb9e859ef84175b173c6e986064f2b861e2931dab9e752d793e7bddcba973946a7586926363010adec36d000000000000000000000000000000000000000000000000000000000000003220000000000000000000000000000000000000000000000000000000000000000040024a14a26afce7d4bf0308b38970b1dadf6adfea2c7501522000000000000000000000000000000000000000000000000000000000000000011241f098ce3832fc5bd467083119292ec1a7e2239784f7734204fdf5e01d84a4e63c2b37cb4a05ef919c5ada1db6775c86dadacaf69f53fd54f09efb326d6b658e0601') 104 | # assert r2['transaction']['nonce'] == '2ba194ecf7c04233b8b87d4f359840a1' 105 | 106 | 107 | def test_client(): 108 | print('读取合约文件...') 109 | simple_class = ContractClass(Path('tests/SimpleStorage.sol'), client) 110 | 111 | print('创建账户...') 112 | account = client.create_key() 113 | user_addr = account['address'] 114 | private_key = account['private'] 115 | 116 | print('合约实例化...') 117 | value = 100 118 | simple_obj, contract_addr, tx_hash = simple_class.instantiate(private_key, value) 119 | with pytest.raises(RuntimeError, match='timeout'): 120 | client.confirm_transaction(tx_hash, 1) # 等待超时. 121 | client.confirm_transaction(tx_hash) 122 | 123 | tx_hash = client.store_abi(private_key, contract_addr, simple_class.get_raw_abi()) 124 | client.confirm_transaction(tx_hash) 125 | client.get_code(contract_addr) == simple_class.get_code() 126 | client.get_abi(contract_addr) == json.loads(simple_class.get_raw_abi()) 127 | 128 | print('修改合约状态...') 129 | new_value = 200 130 | tx_hash = simple_obj.set(new_value) 131 | client.get_transaction_receipt(tx_hash) 132 | 133 | # 读取状态 134 | r = client.call_readonly_func(contract_addr, simple_obj.get.address, from_addr=user_addr) 135 | assert decode_param('uint', r) == value 136 | assert simple_obj.get() == value # 因为交易还未被确认. 137 | client.set_call_mode('pending') 138 | assert simple_obj.get() == new_value # 读取未确认的数据. 139 | 140 | client.confirm_transaction(tx_hash) 141 | 142 | # 读取状态 143 | assert simple_obj.get() == new_value 144 | client.set_call_mode('latest') 145 | assert simple_obj.get() == new_value # 交易已确认. 146 | 147 | 148 | def test_single_call(): 149 | """测试常见的合约调用操作.""" 150 | print('读取合约文件...') 151 | simple_class = ContractClass(Path('tests/SimpleStorage.sol'), client) 152 | double_class = ContractClass(Path('tests/DoubleStorage.sol'), client) 153 | dummy_class = ContractClass(Path('tests/Dummy.sol'), client) 154 | assert simple_class.name == 'SimpleStorage' 155 | assert double_class.name == 'DoubleStorage' 156 | assert dummy_class.name == 'Dummy' 157 | 158 | print('创建账户...') 159 | account = client.create_key() 160 | user_addr = account['address'] 161 | private_key = account['private'] 162 | 163 | # 部署合约, 使用(0, 1, 2)个参数. 164 | print('合约实例化...') 165 | value = 100 166 | simple_obj, contract_addr, _ = simple_class.instantiate(private_key, value) 167 | double_obj, _, _ = double_class.instantiate(private_key, value, value * 10) 168 | dummy_obj, _, tx_hash = dummy_class.instantiate(private_key) 169 | 170 | # 绑定另一个合约对象到contract_addr, 这个对象的行为应该跟simple_obj一样 171 | ref_simple_obj = simple_class.bind(contract_addr, private_key) 172 | 173 | print('等待交易确认...') 174 | client.confirm_transaction(tx_hash) 175 | 176 | with pytest.raises(KeyError, match='not registered'): 177 | simple_obj.bad_call() 178 | 179 | with pytest.raises(AttributeError, match='__getstate__'): 180 | simple_obj.__getstate__() 181 | 182 | assert simple_obj.get() == value 183 | assert ref_simple_obj.get() == value # 测试bind 184 | assert double_obj.x() == value 185 | assert double_obj.y() == value * 10 186 | 187 | assert client.get_transaction(tx_hash)['hash'] == tx_hash 188 | assert client.get_transaction_count(user_addr) == 3 189 | 190 | print('修改合约状态...') 191 | value = 200 192 | tx_hash = simple_obj.set(value) # 使用1个参数. 193 | receipt = client.get_transaction_receipt(tx_hash) 194 | assert receipt['errorMessage'] is None 195 | client.confirm_transaction(tx_hash) 196 | assert simple_obj.get() == value 197 | 198 | v1 = [1, 2] 199 | v2 = [3, 4] 200 | ans = 1 * 3 + 2 * 4 201 | simple_obj.reset() # 使用0个参数 202 | tx_hash = simple_obj.add_by_vec(v1, v2) # 使用2个参数 203 | client.confirm_transaction(tx_hash) 204 | assert simple_obj.get() == ans 205 | 206 | print('修改Quota...') 207 | simple_obj.set.quota = 1 # 设置很小的Quota. 208 | assert simple_obj.set.quota == 1 209 | assert simple_obj.set.name == 'set' 210 | assert simple_obj.set.address.startswith('0x') and len(param_to_bytes(simple_obj.set.address)) == 4 211 | assert simple_obj.set.param_types == 'uint256' 212 | assert simple_obj.set.return_types == '' 213 | assert simple_obj.set.mutable is True 214 | assert simple_obj.add_by_vec.param_types == 'uint256[],uint256[]' 215 | assert simple_obj.add_by_vec.return_types == 'uint256' 216 | 217 | tx_hash = simple_obj.set(300) 218 | with pytest.raises(RuntimeError, match='Not enough base quota.'): 219 | client.get_transaction_receipt(tx_hash) 220 | 221 | assert simple_obj.get() == ans 222 | 223 | 224 | def test_batch_call(): 225 | """测试常见的合约批量调用操作.""" 226 | print('读取合约文件...') 227 | simple_class = ContractClass(Path('tests/SimpleStorage.sol'), client) 228 | double_class = ContractClass(Path('tests/DoubleStorage.sol'), client) 229 | dummy_class = ContractClass(Path('tests/Dummy.sol'), client) 230 | 231 | print('创建账户...') 232 | account = client.create_key() 233 | private_key = account['private'] 234 | 235 | print('批量实例化...') 236 | init_values = [(), (), ()] 237 | obj_list = [] 238 | for dummy_obj, contract_addr, tx_hash in dummy_class.batch_instantiate(private_key, init_values): # 使用0个参数 239 | obj_list.append(dummy_obj) 240 | client.get_transaction_receipt(tx_hash) 241 | assert len(obj_list) == 3 242 | 243 | init_values = [100, 200, (300,)] 244 | obj_list = [] 245 | for value, (simple_obj, contract_addr, tx_hash) in zip(init_values, simple_class.batch_instantiate(private_key, init_values)): # 使用1个参数 246 | client.confirm_transaction(tx_hash) 247 | if value == (300,): 248 | assert simple_obj.get() == value[0] 249 | else: 250 | assert simple_obj.get() == value 251 | obj_list.append(simple_obj) 252 | 253 | init_values = [(100, 200), (300, 400), (500, 600)] 254 | for (x, y), (double_obj, contract_addr, tx_hash) in zip(init_values, double_class.batch_instantiate(private_key, init_values)): # 使用2个参数 255 | client.confirm_transaction(tx_hash) 256 | assert double_obj.x() == x 257 | assert double_obj.y() == y 258 | 259 | print('批量调用reset...') 260 | tx_code_list = [simple_obj.get_tx_code('reset') # 使用0个参数 261 | for simple_obj in obj_list] 262 | tx_hash = client.batch_call_func(private_key, tx_code_list) 263 | client.confirm_transaction(tx_hash) 264 | 265 | for simple_obj in obj_list: 266 | assert simple_obj.get() == 0 267 | 268 | print('批量调用set...') 269 | new_values = [101, 202, (303,)] 270 | tx_code_list = [simple_obj.get_tx_code('set', value) # 使用1个参数 271 | for value, simple_obj in zip(new_values, obj_list)] 272 | tx_hash = client.batch_call_func(private_key, tx_code_list) 273 | client.confirm_transaction(tx_hash) 274 | 275 | for value, simple_obj in zip(new_values, obj_list): 276 | if value == (303,): 277 | assert simple_obj.get() == value[0] 278 | else: 279 | assert simple_obj.get() == value 280 | 281 | 282 | def test_generate_account(): 283 | pri_key, pub_key, addr = client.signer.generate_account() 284 | assert pri_key.startswith('0x') and len(pri_key) == 32 * 2 + 2 285 | assert pub_key.startswith('0x') and len(pub_key) == 64 * 2 + 2 286 | assert addr.startswith('0x') and len(addr) == 20 * 2 + 2 287 | 288 | # 从私钥还原. 289 | pri_key2, pub_key2, addr2 = client.signer.generate_account(param_to_bytes(pri_key)) 290 | assert pri_key == pri_key2 291 | assert pub_key == pub_key2 292 | assert addr == addr2 293 | --------------------------------------------------------------------------------