├── .ci ├── travis-after-success.sh ├── travis-before-install.sh ├── travis-before-script.sh ├── travis-install.sh └── travis_script.sh ├── .coveragerc ├── .editorconfig ├── .github ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .pre-commit-config.yaml ├── .travis.yml ├── AUTHORS.rst ├── CHANGELOG.rst ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.rst ├── LICENSE ├── LICENSES.md ├── MANIFEST.in ├── Makefile ├── README.rst ├── RELEASE_PROCESS.md ├── bigchaindb_driver ├── __init__.py ├── common │ ├── __init__.py │ ├── crypto.py │ ├── exceptions.py │ ├── transaction.py │ └── utils.py ├── connection.py ├── crypto.py ├── driver.py ├── exceptions.py ├── offchain.py ├── pool.py ├── transport.py └── utils.py ├── codecov.yml ├── compose ├── bigchaindb_driver │ └── Dockerfile └── bigchaindb_server │ └── Dockerfile ├── docker-compose.yml ├── docs ├── Makefile ├── _static │ ├── cc_escrow_execute_abort.png │ ├── tx_escrow_execute_abort.png │ ├── tx_multi_condition_multi_fulfillment_v1.png │ └── tx_single_condition_single_fulfillment_v1.png ├── aboutthedocs.rst ├── advanced-installation.rst ├── advanced-usage.rst ├── authors.rst ├── changelog.rst ├── conf.py ├── connect.rst ├── contributing.rst ├── handcraft.rst ├── index.rst ├── libref.rst ├── make.bat ├── quickstart.rst ├── requirements.txt ├── upgrading.rst └── usage.rst ├── media └── repo-banner@2x.png ├── pytest.ini ├── requirements_dev.txt ├── setup.py ├── tests ├── __init__.py ├── conftest.py ├── test_connection.py ├── test_crypto.py ├── test_driver.py ├── test_exceptions.py ├── test_offchain.py ├── test_pool.py ├── test_transport.py └── test_utils.py ├── tox.ini └── travis_pypi_setup.py /.ci/travis-after-success.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright BigchainDB GmbH and BigchainDB contributors 3 | # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) 4 | # Code is Apache-2.0 and docs are CC-BY-4.0 5 | 6 | 7 | set -e -x 8 | 9 | if [ "${TOXENV}" == "py36" ]; then 10 | codecov -v -f htmlcov/coverage.xml 11 | fi 12 | -------------------------------------------------------------------------------- /.ci/travis-before-install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright BigchainDB GmbH and BigchainDB contributors 3 | # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) 4 | # Code is Apache-2.0 and docs are CC-BY-4.0 5 | 6 | 7 | if [[ "${TOXENV}" == "py35" || "${TOXENV}" == "py36" ]]; then 8 | sudo apt-get update 9 | sudo apt-get -y -o Dpkg::Options::="--force-confnew" install docker-ce 10 | 11 | sudo rm /usr/local/bin/docker-compose 12 | curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > docker-compose 13 | chmod +x docker-compose 14 | sudo mv docker-compose /usr/local/bin 15 | fi 16 | -------------------------------------------------------------------------------- /.ci/travis-before-script.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright BigchainDB GmbH and BigchainDB contributors 3 | # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) 4 | # Code is Apache-2.0 and docs are CC-BY-4.0 5 | 6 | 7 | set -e -x 8 | 9 | if [[ "${TOXENV}" == "py35" || "${TOXENV}" == "py36" ]]; then 10 | docker-compose up -d bdb 11 | fi 12 | -------------------------------------------------------------------------------- /.ci/travis-install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright BigchainDB GmbH and BigchainDB contributors 3 | # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) 4 | # Code is Apache-2.0 and docs are CC-BY-4.0 5 | 6 | 7 | set -e -x 8 | 9 | pip install --upgrade pip 10 | pip install --upgrade tox 11 | 12 | if [[ "${TOXENV}" == "py35" || "${TOXENV}" == "py36" ]]; then 13 | docker-compose build --no-cache bigchaindb bigchaindb-driver 14 | pip install --upgrade codecov 15 | fi 16 | -------------------------------------------------------------------------------- /.ci/travis_script.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright BigchainDB GmbH and BigchainDB contributors 3 | # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) 4 | # Code is Apache-2.0 and docs are CC-BY-4.0 5 | 6 | 7 | set -e -x 8 | 9 | if [[ "${TOXENV}" == "py35" || "${TOXENV}" == "py36" ]]; then 10 | docker-compose run --rm bigchaindb-driver pytest -v --cov=bigchaindb_driver --cov-report xml:htmlcov/coverage.xml 11 | else 12 | tox -e ${TOXENV} 13 | fi 14 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | source = . 3 | omit = *test*, docs/*, bigchaindb_driver/common/*, setup.py 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 4 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | charset = utf-8 11 | end_of_line = lf 12 | 13 | [*.bat] 14 | indent_style = tab 15 | end_of_line = crlf 16 | 17 | [LICENSE] 18 | insert_final_newline = false 19 | 20 | [Makefile] 21 | indent_style = tab 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | * bigchaindb-driver version: 2 | * bigchaindb **server** version (`bigchaindb -v`): 3 | * Python version: 4 | * Operating System: 5 | 6 | ### Description 7 | 8 | Describe what you were trying to get done. 9 | Tell us what happened, what went wrong, and what you expected to happen. 10 | 11 | ### What I Did 12 | 13 | ``` 14 | Paste the command(s) you ran and the output. 15 | If there was a crash, please include the traceback here. 16 | ``` 17 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | A few sentences describing the overall goals of the pull request's commits. 3 | 4 | ## Issues This PR Fixes 5 | Fixes #NNNN 6 | Fixes #NNNN 7 | 8 | ## Related PRs 9 | List related PRs against other branches e.g. for backporting features/bugfixes 10 | to previous release branches: 11 | 12 | Repo/Branch | PR 13 | ------ | ------ 14 | some_other_PR | [link]() 15 | 16 | 17 | ## Todos 18 | - [ ] Tested and working on development environment 19 | - [ ] Unit tests (if appropriate) 20 | - [ ] Added/Updated all related documentation. Add [link]() if different from this PR 21 | - [ ] DevOps Support needed e.g. create Runscope API test if new endpoint added or 22 | update deployment docs. Create a ticket and add [link]() 23 | 24 | ## Deployment Notes 25 | Notes about how to deploy this work. For example, running a migration against the production DB. 26 | 27 | ## How to QA 28 | Outline the steps to test or reproduce the PR here. 29 | 30 | ## Impacted Areas in Application 31 | List general components of the application that this PR will affect: 32 | - Scale 33 | - Performance 34 | - Security etc. -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | 55 | # Sphinx documentation 56 | docs/_build/ 57 | 58 | # PyBuilder 59 | target/ 60 | 61 | # bigchaindb configuration 62 | .bigchaindb 63 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # Copyright BigchainDB GmbH and BigchainDB contributors 2 | # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) 3 | # Code is Apache-2.0 and docs are CC-BY-4.0 4 | 5 | repos: 6 | - repo: git://github.com/pre-commit/pre-commit-hooks 7 | sha: v1.1.1 8 | hooks: 9 | - id: trailing-whitespace 10 | args: ['--no-markdown-linebreak-ext'] 11 | - id: check-merge-conflict 12 | - id: debug-statements 13 | - id: check-added-large-files 14 | - id: flake8 15 | 16 | - repo: git://github.com/chewse/pre-commit-mirrors-pydocstyle 17 | sha: v2.1.1 18 | hooks: 19 | - id: pydocstyle 20 | # list of error codes to check, see: http://www.pydocstyle.org/en/latest/error_codes.html 21 | args: ['--select=D204,D201,D209,D210,D212,D300,D403'] 22 | 23 | # negate the exclude to only apply the hooks to 'bigchaindb_driver' and 'tests' folder 24 | exclude: '^(?!bigchaindb_driver/)(?!tests/)' 25 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Copyright BigchainDB GmbH and BigchainDB contributors 2 | # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) 3 | # Code is Apache-2.0 and docs are CC-BY-4.0 4 | 5 | language: python 6 | python: 7 | - 3.5 8 | - 3.6 9 | 10 | env: 11 | global: 12 | - DOCKER_COMPOSE_VERSION=1.19.0 13 | matrix: 14 | - TOXENV=py35 15 | - TOXENV=py36 16 | - TOXENV=flake8 17 | - TOXENV=docs 18 | 19 | matrix: 20 | fast_finish: true 21 | exclude: 22 | - python: 3.5 23 | env: TOXENV=flake8 24 | - python: 3.5 25 | env: TOXENV=docs 26 | - python: 3.5 27 | env: TOXENV=py36 28 | - python: 3.6 29 | env: TOXENV=py35 30 | 31 | before_install: 32 | - sudo .ci/travis-before-install.sh 33 | 34 | install: 35 | - .ci/travis-install.sh 36 | 37 | before_script: 38 | - .ci/travis-before-script.sh 39 | 40 | script: .ci/travis_script.sh 41 | 42 | after_success: 43 | - .ci/travis-after-success.sh 44 | -------------------------------------------------------------------------------- /AUTHORS.rst: -------------------------------------------------------------------------------- 1 | 2 | .. Copyright BigchainDB GmbH and BigchainDB contributors 3 | SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) 4 | Code is Apache-2.0 and docs are CC-BY-4.0 5 | 6 | ======= 7 | Credits 8 | ======= 9 | 10 | Development Lead 11 | ---------------- 12 | 13 | BigchainDB GmbH 14 | -------------------------------------------------------------------------------- /CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | 2 | .. Copyright BigchainDB GmbH and BigchainDB contributors 3 | SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) 4 | Code is Apache-2.0 and docs are CC-BY-4.0 5 | 6 | Changelog 7 | ========= 8 | 9 | 0.6.2 (2018-11-03) 10 | ------------------ 11 | Changed 12 | ^^^^^^^ 13 | * In setup.py, changed python-rapidjson==0.6.0 to ~=0.6.0, 14 | and changed requests>=2.11.0 to >=2.20.0 15 | 16 | 0.6.1 (2018-10-21) 17 | ------------------ 18 | Fixed 19 | ^^^^^ 20 | * Fixed the problem with a docs page (Handcrafting Transactions) that wouldn't build. 21 | 22 | 0.6.0 (2018-10-20) 23 | ------------------ 24 | Changed 25 | ^^^^^^^ 26 | * Added support for deterministic keypair generation from a 32-byte seed. 27 | See pull request #487 by external contributor @excerebrose 28 | * Pinned cryptoconditions==0.8.0 in setup.py 29 | 30 | Removed 31 | ^^^^^^^ 32 | * The send() function was removed. See pull request #483. 33 | 34 | Known issues 35 | ^^^^^^^^^^^^ 36 | * Builds of the Handcrafting Transactions page started failing again, 37 | in Travis CI and on ReadTheDocs. 38 | 39 | 0.5.3 (2018-09-12) 40 | ------------------ 41 | Changed 42 | ^^^^^^^ 43 | * Fixed a failing unit test 44 | * Pinned cryptoconditions==0.7.2 in setup.py 45 | * Fixed the Handcrafting Transactions page in the docs 46 | 47 | 0.5.2 (2018-08-31) 48 | ------------------- 49 | Added 50 | ^^^^^ 51 | 52 | * Cap exponential backoff depending on timeout value for reasonable waiting time in event of network recovery. `#470 ` 53 | * Update cryptoconditions dependency because of security vulnerability CVE-2018-10903. `#472 ` 54 | 55 | 56 | 0.5.1 (2018-08-23) 57 | --------------------- 58 | Added 59 | ^^^^^ 60 | 61 | * Support for BigchainDB server v2.0.0.b5. 62 | * added round-robin strategy to connect to nodes of the BigchainDB network `BEP 14 `_ 63 | 64 | 0.5.0 (2018-06-14) 65 | --------------------- 66 | Added 67 | ^^^^^ 68 | * Added three new methods to send/post a transaction as discussed `here `_: 69 | 70 | * ``send_commit`` 71 | * ``send_async`` 72 | * ``send_sync`` 73 | 74 | Deprecated 75 | ^^^^^^^^^^ 76 | * ``send()`` under ``TransactionEndpoint``, and available 77 | via ``BigchainDB.transactions``. Replaced by the above three methods: 78 | ``send_commit()``, ``send_async()``, and ``send_sync()``. 79 | 80 | 81 | 0.5.0a4 (2018-05-07) 82 | --------------------- 83 | * `Removed dependencies from BigchainDB Server package `_. 84 | 85 | 86 | 0.5.0a2 (2018-04-18) 87 | --------------------- 88 | * `The default mode for sending a transaction is now commit `_. 89 | * `The metadata endpoint was added `_. 90 | * Support for BigchainDB server v2.0.0a2. 91 | 92 | 93 | 0.5.0a1 (2018-04-03) 94 | -------------------- 95 | There were **many** changes between BigchainDB 1.3 and BigchainDB 2.0 Alpha, too many to list here. We wrote a series of blog posts to summarize most changes, especially those that affect end users and application developers: 96 | 97 | * `Some HTTP API Changes in the Next Release `_. 98 | * `Three Transaction Model Changes in the Next Release `_. 99 | 100 | 101 | 0.4.1 (2017-08-02) 102 | ------------------ 103 | Fixed 104 | ^^^^^ 105 | * Handcrafting transactions documentation. `Pull request #312 106 | `_. 107 | * Quickstart guide. `Pull request #316 108 | `_. 109 | 110 | 0.4.0 (2017-07-05) 111 | ------------------ 112 | Added 113 | ^^^^^ 114 | * Support for BigchainDB server (HTTP API) 1.0.0. 115 | 116 | 0.3.0 (2017-06-23) 117 | ------------------ 118 | Added 119 | ^^^^^ 120 | * Support for BigchainDB server (HTTP API) 1.0.0rc1. 121 | * Support for crypto-conditions RFC draft version 02. 122 | * Added support for text search endpoint ``/assets?search=`` 123 | 124 | 0.2.0 (2017-02-06) 125 | ------------------ 126 | Added 127 | ^^^^^ 128 | * Support for BigchainDB server 0.9. 129 | * Methods for ``GET /`` and ``GET /api/v1`` 130 | 131 | Changed 132 | ^^^^^^^ 133 | * Node URLs, passed to ``BigchainDB()`` MUST not include the api prefix 134 | ``'/api/v1'``, e.g.: 135 | 136 | * BEFORE: ``http://localhost:9984/api/v1`` 137 | * NOW: ``http://localhost:9984`` 138 | 139 | 0.1.0 (2016-11-29) 140 | ------------------ 141 | Added 142 | ^^^^^ 143 | * Support for BigchainDB server 0.8.0. 144 | * Support for divisible assets. 145 | 146 | Removed 147 | ^^^^^^^ 148 | * ``create()`` and ``transfer()`` under ``TransactionEndpoint``, and available 149 | via ``BigchainDB.transactions``. Replaced by the three "canonical" 150 | transaction operations: ``prepare()``, ``fulfill()``, and ``send()``. 151 | * Support for client side timestamps. 152 | 153 | 154 | 0.0.3 (2016-11-25) 155 | ------------------ 156 | Added 157 | ^^^^^ 158 | * Support for "canonical" transaction operations: 159 | 160 | * ``prepare`` 161 | * ``fulfill`` 162 | * ``send`` 163 | 164 | Deprecated 165 | ^^^^^^^^^^ 166 | * ``create()`` and ``transfer()`` under ``TransactionEndpoint``, and available 167 | via ``BigchainDB.transactions``. Replaced by the above three "canonical" 168 | transaction operations: ``prepare()``, ``fulfill()``, and ``send()``. 169 | 170 | Fixed 171 | ^^^^^ 172 | * ``BigchainDB()`` default node setting on its transport class. See commit 173 | `0a80206 `_ 174 | 175 | 176 | 0.0.2 (2016-10-28) 177 | ------------------ 178 | 179 | Added 180 | ^^^^^ 181 | * Support for BigchainDB server 0.7.0 182 | 183 | 184 | 0.0.1dev1 (2016-08-25) 185 | ---------------------- 186 | 187 | * Development (pre-alpha) release on PyPI. 188 | 189 | Added 190 | ^^^^^ 191 | * Minimal support for ``POST`` (via ``create()`` and ``transfer()``), and 192 | ``GET`` operations on the ``/transactions`` endpoint. 193 | 194 | 195 | 0.0.1a1 (2016-08-12) 196 | -------------------- 197 | 198 | * Planning release on PyPI. 199 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | See [https://github.com/bigchaindb/bigchaindb/blob/master/CODE_OF_CONDUCT.md](https://github.com/bigchaindb/bigchaindb/blob/master/CODE_OF_CONDUCT.md) 8 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | 2 | .. Copyright BigchainDB GmbH and BigchainDB contributors 3 | SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) 4 | Code is Apache-2.0 and docs are CC-BY-4.0 5 | 6 | .. highlight:: shell 7 | 8 | ============ 9 | Contributing 10 | ============ 11 | 12 | Contributions are welcome, and they are greatly appreciated! Every 13 | little bit helps, and credit will always be given. 14 | If you want to read about our guidelines for contributing, we are using C4 and you can't find it here: `C4`_ 15 | 16 | You can contribute in many ways: 17 | 18 | Types of Contributions 19 | ---------------------- 20 | 21 | Report Bugs 22 | ~~~~~~~~~~~ 23 | 24 | Report bugs at https://github.com/bigchaindb/bigchaindb-driver/issues. 25 | 26 | If you are reporting a bug, please include: 27 | 28 | * Your operating system name and version. 29 | * Any details about your local setup that might be helpful in troubleshooting. 30 | * Detailed steps to reproduce the bug. 31 | 32 | Fix Bugs 33 | ~~~~~~~~ 34 | 35 | Look through the GitHub issues for bugs. Anything tagged with "bug" 36 | and "help wanted" is open to whoever wants to implement it. 37 | 38 | Implement Features 39 | ~~~~~~~~~~~~~~~~~~ 40 | 41 | Look through the GitHub issues for features. Anything tagged with "enhancement" 42 | and "help wanted" is open to whoever wants to implement it. 43 | 44 | Make a Feature Request or Proposal 45 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 46 | To make a feature request or proposal, write a `BigchaindB Enhancement Proposal (BEP)`_: 47 | 48 | We use `COSS` to handle BEPs, you can read about it here: `COSS`_ 49 | 50 | Write Documentation 51 | ~~~~~~~~~~~~~~~~~~~ 52 | 53 | bigchaindb-driver could always use more documentation, whether as part of the 54 | official bigchaindb-driver docs, in docstrings, or even on the web in blog posts, 55 | articles, and such. 56 | 57 | Submit Feedback 58 | ~~~~~~~~~~~~~~~ 59 | 60 | The best way to send feedback is to file an issue at https://github.com/bigchaindb/bigchaindb-driver/issues. 61 | 62 | If you are proposing a feature: 63 | 64 | * Explain in detail how it would work. 65 | * Keep the scope as narrow as possible, to make it easier to implement. 66 | * Remember that this is a volunteer-driven project, and that contributions 67 | are welcome :) 68 | 69 | Get Started! 70 | ------------ 71 | 72 | Ready to contribute? 73 | See the :doc:`Installation Guide for Developers ` page. 74 | 75 | 76 | Pull Request Guidelines 77 | ----------------------- 78 | 79 | Before you submit a pull request, check that it meets these guidelines: 80 | 81 | 1. The pull request should include tests. 82 | 2. If the pull request adds functionality, the docs should be updated. Put 83 | your new functionality into a function with a docstring, and add the 84 | feature to the list in README.rst. 85 | 3. The pull request should work for Python 3.5, and pass the flake8 check. 86 | Check https://travis-ci.org/bigchaindb/bigchaindb-driver/pull_requests 87 | and make sure that the tests pass for all supported Python versions. 88 | 4. Follow the pull request template while creating new PRs, the template will 89 | be visible to you when you create a new pull request. 90 | 91 | Tips 92 | ---- 93 | 94 | .. _devenv-docker: 95 | 96 | Development Environment with Docker 97 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 98 | Depending on what you are doing, you may need to run at least one BigchainDB 99 | node. You can use the `docker-compose.yml`_ file to run a node, and perform 100 | other tasks that depend on the running node. To run a BigchainDB node, (for 101 | development), you start a MongoDB and Tendermint node, followed by the linked BigchainDB 102 | node:: 103 | 104 | # Implicitly creates a MongoDB and Tendermint instance 105 | $ docker-compose up -d bigchaindb 106 | 107 | You can monitor the logs:: 108 | 109 | $ docker-compose logs -f 110 | 111 | Additionally, we have a nice Makefile to make things easier for everyone. Some helpful commands are 112 | 113 | .. code-block:: python 114 | 115 | >>> make 116 | install Install the package to the active Python's site-packages 117 | start Run BigchainDB driver from source and daemonize it (stop with make stop) 118 | stop Stop BigchainDB driver 119 | reset Stop and REMOVE all containers. WARNING: you will LOSE all data stored in BigchainDB server. 120 | test Run all tests once or specify a file/test with TEST=tests/file.py::Class::test 121 | test-watch Run all, or only one with TEST=tests/file.py::Class::test, tests and wait. Every time you change code, test/s will be run again. 122 | docs Generate Sphinx HTML documentation, including API docs 123 | lint Check style with flake8 124 | cov Check code coverage and open the result in the browser 125 | clean Remove all build, test, coverage and Python artifacts 126 | release package and upload a release 127 | dist builds source (and not for now, wheel package) 128 | clean-build Remove build artifacts 129 | clean-pyc Remove Python file artifacts 130 | clean-test Remove test and coverage artifacts 131 | 132 | Tests 133 | ~~~~~ 134 | 135 | To run a subset of tests:: 136 | 137 | $ docker-compose run --rm bigchaindb-driver pytest -v tests/test_driver.py 138 | 139 | .. important:: When running tests, unless you are targeting a test that does 140 | not require a connection with the BigchainDB server, you need to run the 141 | BigchainDB, MongoDB and Tendermint servers:: 142 | 143 | $ docker-compose up -d bigchaindb 144 | 145 | 146 | Dependency on Bigchaindb 147 | ~~~~~~~~~~~~~~~~~~~~~~~~ 148 | 149 | By default, the development requirements, `BigchainDB server Dockerfile `_, 150 | and `.travis.yml `_ 151 | are set to depend from BigchainDB's master branch to more easily track changes 152 | against BigchainDB's API. 153 | 154 | 155 | .. _docker-compose.yml: https://github.com/bigchaindb/bigchaindb-driver/blob/master/docker-compose.yml 156 | .. _BigchaindB Enhancement Proposal (BEP): https://github.com/bigchaindb/BEPs 157 | .. _C4: https://github.com/bigchaindb/BEPs/tree/master/1 158 | .. _COSS: https://github.com/bigchaindb/BEPs/tree/master/2 159 | 160 | -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /LICENSES.md: -------------------------------------------------------------------------------- 1 | # Code Licenses 2 | 3 | All code in _this_ repository (including short code snippets embedded in the official BigchainDB _documentation_) is licensed under the Apache Software License 2.0, the full text of which can be found at [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0). 4 | 5 | For the licenses on all other BigchainDB-related code, see the license file in the associated repository. 6 | 7 | # Documentation Licenses 8 | 9 | The official BigchainDB documentation, _except for the short code snippets embedded within it_, is licensed under a Creative Commons Attribution 4.0 International license, the full text of which can be found at [http://creativecommons.org/licenses/by/4.0/legalcode](http://creativecommons.org/licenses/by/4.0/legalcode). 10 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include AUTHORS.rst 2 | include CONTRIBUTING.rst 3 | include CHANGELOG.rst 4 | include LICENSE 5 | include HACKING.rst 6 | include README.rst 7 | 8 | recursive-include tests * 9 | recursive-exclude * __pycache__ 10 | recursive-exclude * *.py[co] 11 | 12 | recursive-include docs *.rst conf.py Makefile make.bat *.jpg *.png *.gif 13 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: help install start stop reset test test-watch docs lint cov clean release root-url dist check-deps clean-build clean-pyc clean-test 2 | .DEFAULT_GOAL := help 3 | 4 | ############################# 5 | # Open a URL in the browser # 6 | ############################# 7 | define BROWSER_PYSCRIPT 8 | import os, webbrowser, sys 9 | try: 10 | from urllib import pathname2url 11 | except: 12 | from urllib.request import pathname2url 13 | 14 | webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1]))) 15 | endef 16 | export BROWSER_PYSCRIPT 17 | 18 | ################################## 19 | # Display help for this makefile # 20 | ################################## 21 | define PRINT_HELP_PYSCRIPT 22 | import re, sys 23 | 24 | print("BigchainDB Driver 0.5 developer toolbox") 25 | print("---------------------------------------") 26 | print("Usage: make COMMAND") 27 | print("") 28 | print("Commands:") 29 | for line in sys.stdin: 30 | match = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line) 31 | if match: 32 | target, help = match.groups() 33 | print(" %-16s %s" % (target, help)) 34 | endef 35 | export PRINT_HELP_PYSCRIPT 36 | 37 | ################## 38 | # Basic commands # 39 | ################## 40 | DOCKER := docker 41 | DC := docker-compose 42 | BROWSER := python -c "$$BROWSER_PYSCRIPT" 43 | HELP := python -c "$$PRINT_HELP_PYSCRIPT" 44 | ECHO := /usr/bin/env echo 45 | IS_DOCKER_COMPOSE_INSTALLED := $(shell command -v docker-compose 2> /dev/null) 46 | 47 | # User-friendly check for sphinx-build 48 | help: 49 | @$(HELP) < $(MAKEFILE_LIST) 50 | 51 | install: clean ## Install the package to the active Python's site-packages 52 | python setup.py install 53 | 54 | start: check-deps ## Run BigchainDB driver from source and daemonize it (stop with `make stop`) 55 | @$(DC) up -d bigchaindb 56 | 57 | stop: check-deps ## Stop BigchainDB driver 58 | @$(DC) stop 59 | 60 | reset: check-deps ## Stop and REMOVE all containers. WARNING: you will LOSE all data stored in BigchainDB server. 61 | @$(DC) down 62 | 63 | test: check-deps ## Run all tests once or specify a file/test with TEST=tests/file.py::Class::test 64 | @$(DC) up -d bdb 65 | @$(DC) run --rm bigchaindb-driver pytest ${TEST} -v 66 | 67 | test-watch: check-deps ## Run all, or only one with TEST=tests/file.py::Class::test, tests and wait. Every time you change code, test/s will be run again. 68 | @$(DC) run --rm bigchaindb-driver pytest ${TEST} -f -v 69 | 70 | docs: ## Generate Sphinx HTML documentation, including API docs 71 | @$(DC) run --rm --no-deps bdocs make -C docs html 72 | $(BROWSER) docs/_build/html/index.html 73 | 74 | lint: check-deps ## Check style with flake8 75 | @$(DC) run --rm bigchaindb-driver flake8 bigchaindb_driver tests 76 | 77 | cov: check-deps ## Check code coverage and open the result in the browser 78 | @$(DC) run --rm bigchaindb-driver pytest -v --cov=bigchaindb_driver --cov-report html 79 | $(BROWSER) htmlcov/index.html 80 | 81 | clean: clean-build clean-pyc clean-test ## Remove all build, test, coverage and Python artifacts 82 | @$(ECHO) "Cleaning was successful." 83 | 84 | release: dist ## package and upload a release 85 | twine upload dist/* 86 | 87 | root-url: 88 | @$(DC) port bigchaindb 9984 89 | 90 | ############### 91 | # Sub targets # 92 | ############### 93 | 94 | dist: clean ## builds source (and not for now, wheel package) 95 | python setup.py sdist 96 | ls -l dist 97 | 98 | check-deps: 99 | ifndef IS_DOCKER_COMPOSE_INSTALLED 100 | @$(ECHO) "Error: docker-compose is not installed" 101 | @$(ECHO) 102 | @$(ECHO) "You need docker-compose to run this command. Check out the official docs on how to install it in your system:" 103 | @$(ECHO) "- https://docs.docker.com/compose/install/" 104 | @$(ECHO) 105 | @$(DC) # docker-compose is not installed, so we call it to generate an error and exit 106 | endif 107 | 108 | clean-build: ## Remove build artifacts 109 | @rm -fr build/ 110 | @rm -fr dist/ 111 | @rm -fr .eggs/ 112 | @find . -name '*.egg-info' -exec rm -fr {} + 113 | @find . -name '*.egg' -exec rm -f {} + 114 | 115 | clean-pyc: ## Remove Python file artifacts 116 | @find . -name '*.pyc' -exec rm -f {} + 117 | @find . -name '*.pyo' -exec rm -f {} + 118 | @find . -name '*~' -exec rm -f {} + 119 | @find . -name '__pycache__' -exec rm -fr {} + 120 | 121 | clean-test: ## Remove test and coverage artifacts 122 | @rm -fr .tox/ 123 | @rm -f .coverage 124 | @rm -fr htmlcov/ 125 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | 2 | .. Copyright BigchainDB GmbH and BigchainDB contributors 3 | SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) 4 | Code is Apache-2.0 and docs are CC-BY-4.0 5 | 6 | .. image:: media/repo-banner@2x.png 7 | 8 | .. image:: https://badges.gitter.im/bigchaindb/bigchaindb-driver.svg 9 | :alt: Join the chat at https://gitter.im/bigchaindb/bigchaindb-driver 10 | :target: https://gitter.im/bigchaindb/bigchaindb-driver?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge 11 | 12 | 13 | .. image:: https://img.shields.io/pypi/v/bigchaindb-driver.svg 14 | :target: https://pypi.python.org/pypi/bigchaindb-driver 15 | 16 | .. image:: https://img.shields.io/travis/bigchaindb/bigchaindb-driver/master.svg 17 | :target: https://travis-ci.com/bigchaindb/bigchaindb-driver 18 | 19 | .. image:: https://img.shields.io/codecov/c/github/bigchaindb/bigchaindb-driver/master.svg 20 | :target: https://codecov.io/github/bigchaindb/bigchaindb-driver?branch=master 21 | 22 | .. image:: https://readthedocs.org/projects/bigchaindb-python-driver/badge/?version=latest 23 | :target: http://bigchaindb.readthedocs.io/projects/py-driver/en/latest/?badge=latest 24 | :alt: Documentation Status 25 | 26 | BigchainDB Python Driver 27 | ========================== 28 | 29 | * Free software: Apache Software License 2.0 30 | * Check our `Documentation`_ 31 | 32 | .. contents:: Table of Contents 33 | 34 | 35 | Features 36 | -------- 37 | 38 | * Support for preparing, fulfilling, and sending transactions to a BigchainDB 39 | node. 40 | * Retrieval of transactions by id. 41 | 42 | Install 43 | ---------- 44 | 45 | The instructions below were tested on Ubuntu 16.04 LTS. They should also work on other Linux distributions and on macOS. The driver might work on Windows as well, but we do not guarantee it. We recommend to set up (e.g. via Docker on Windows) an Ubuntu VM there. 46 | 47 | We recommend you use a virtual environment to install and update to the latest stable version using `pip` (or `pip3`): 48 | 49 | .. code-block:: text 50 | 51 | pip install -U bigchaindb-driver 52 | 53 | That will install the latest *stable* BigchainDB Python Driver. If you want to install an Alpha, Beta or RC version of the Python Driver, use something like: 54 | 55 | .. code-block:: text 56 | 57 | pip install -U bigchaindb_driver==0.5.0a4 58 | 59 | The above command will install version 0.5.0a4 (Alpha 4). You can find a list of all versions in `the release history page on PyPI `_. 60 | 61 | More information on how to install the driver can be found in the `Quickstart`_ 62 | 63 | BigchainDB Documentation 64 | ------------------------------------ 65 | * `BigchainDB Server Quickstart`_ 66 | * `The Hitchhiker's Guide to BigchainDB`_ 67 | * `HTTP API Reference`_ 68 | * `All BigchainDB Documentation`_ 69 | 70 | Usage 71 | ---------- 72 | Example: Create a divisible asset for Alice who issues 10 token to Bob so that he can use her Game Boy. 73 | Afterwards Bob spends 3 of these tokens. 74 | 75 | If you want to send a transaction you need to `Determine the BigchainDB Root URL`_. 76 | 77 | .. code-block:: python 78 | 79 | # import BigchainDB and create an object 80 | from bigchaindb_driver import BigchainDB 81 | bdb_root_url = 'https://example.com:9984' 82 | bdb = BigchainDB(bdb_root_url) 83 | 84 | # generate a keypair 85 | from bigchaindb_driver.crypto import generate_keypair 86 | alice, bob = generate_keypair(), generate_keypair() 87 | 88 | # create a digital asset for Alice 89 | game_boy_token = { 90 | 'data': { 91 | 'token_for': { 92 | 'game_boy': { 93 | 'serial_number': 'LR35902' 94 | } 95 | }, 96 | 'description': 'Time share token. Each token equals one hour of usage.', 97 | }, 98 | } 99 | 100 | # prepare the transaction with the digital asset and issue 10 tokens for Bob 101 | prepared_token_tx = bdb.transactions.prepare( 102 | operation='CREATE', 103 | signers=alice.public_key, 104 | recipients=[([bob.public_key], 10)], 105 | asset=game_boy_token) 106 | 107 | # fulfill and send the transaction 108 | fulfilled_token_tx = bdb.transactions.fulfill( 109 | prepared_token_tx, 110 | private_keys=alice.private_key) 111 | bdb.transactions.send_commit(fulfilled_token_tx) 112 | 113 | # Use the tokens 114 | # create the output and inout for the transaction 115 | transfer_asset = {'id': fulfilled_token_tx['id']} 116 | output_index = 0 117 | output = fulfilled_token_tx['outputs'][output_index] 118 | transfer_input = {'fulfillment': output['condition']['details'], 119 | 'fulfills': {'output_index': output_index, 120 | 'transaction_id': transfer_asset['id']}, 121 | 'owners_before': output['public_keys']} 122 | 123 | # prepare the transaction and use 3 tokens 124 | prepared_transfer_tx = bdb.transactions.prepare( 125 | operation='TRANSFER', 126 | asset=transfer_asset, 127 | inputs=transfer_input, 128 | recipients=[([alice.public_key], 3), ([bob.public_key], 7)]) 129 | 130 | # fulfill and send the transaction 131 | fulfilled_transfer_tx = bdb.transactions.fulfill( 132 | prepared_transfer_tx, 133 | private_keys=bob.private_key) 134 | sent_transfer_tx = bdb.transactions.send_commit(fulfilled_transfer_tx) 135 | 136 | Compatibility Matrix 137 | -------------------- 138 | 139 | +-----------------------+---------------------------+ 140 | | **BigchainDB Server** | **BigchainDB Driver** | 141 | +=======================+===========================+ 142 | | ``>= 2.0.0b7`` | ``0.6.2`` | 143 | +-----------------------+---------------------------+ 144 | | ``>= 2.0.0b7`` | ``0.6.1`` | 145 | +-----------------------+---------------------------+ 146 | | ``>= 2.0.0b7`` | ``0.6.0`` | 147 | +-----------------------+---------------------------+ 148 | | ``>= 2.0.0b5`` | ``0.5.3`` | 149 | +-----------------------+---------------------------+ 150 | | ``>= 2.0.0b5`` | ``0.5.2`` | 151 | +-----------------------+---------------------------+ 152 | | ``>= 2.0.0b5`` | ``0.5.1`` | 153 | +-----------------------+---------------------------+ 154 | | ``>= 2.0.0b1`` | ``0.5.0`` | 155 | +-----------------------+---------------------------+ 156 | | ``>= 2.0.0a3`` | ``0.5.0a4`` | 157 | +-----------------------+---------------------------+ 158 | | ``>= 2.0.0a2`` | ``0.5.0a2`` | 159 | +-----------------------+---------------------------+ 160 | | ``>= 2.0.0a1`` | ``0.5.0a1`` | 161 | +-----------------------+---------------------------+ 162 | | ``>= 1.0.0`` | ``0.4.x`` | 163 | +-----------------------+---------------------------+ 164 | | ``== 1.0.0rc1`` | ``0.3.x`` | 165 | +-----------------------+---------------------------+ 166 | | ``>= 0.9.1`` | ``0.2.x`` | 167 | +-----------------------+---------------------------+ 168 | | ``>= 0.8.2`` | ``>= 0.1.3`` | 169 | +-----------------------+---------------------------+ 170 | 171 | `Although we do our best to keep the master branches in sync, there may be 172 | occasional delays.` 173 | 174 | License 175 | -------- 176 | * `licenses`_ - open source & open content 177 | 178 | Credits 179 | ------- 180 | 181 | This package was initially created using Cookiecutter_ and the `audreyr/cookiecutter-pypackage`_ project template. Many BigchainDB developers have contributed since then. 182 | 183 | .. _Documentation: https://docs.bigchaindb.com/projects/py-driver/ 184 | .. _pypi history: https://pypi.org/project/bigchaindb-driver/#history 185 | .. _Quickstart: https://docs.bigchaindb.com/projects/py-driver/en/latest/quickstart.html 186 | .. _BigchainDB Server Quickstart: https://docs.bigchaindb.com/projects/server/en/latest/quickstart.html 187 | .. _The Hitchhiker's Guide to BigchainDB: https://www.bigchaindb.com/developers/guide/ 188 | .. _HTTP API Reference: https://docs.bigchaindb.com/projects/server/en/latest/http-client-server-api.html 189 | .. _All BigchainDB Documentation: https://docs.bigchaindb.com/ 190 | .. _Determine the BigchainDB Root URL: https://docs.bigchaindb.com/projects/py-driver/en/latest/connect.html 191 | .. _licenses: https://github.com/bigchaindb/bigchaindb-driver/blob/master/LICENSES.md 192 | .. _Cookiecutter: https://github.com/audreyr/cookiecutter 193 | .. _`audreyr/cookiecutter-pypackage`: https://github.com/audreyr/cookiecutter-pypackage 194 | -------------------------------------------------------------------------------- /RELEASE_PROCESS.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | # Our Release Process 8 | 9 | ## Notes 10 | 11 | BigchainDB follows 12 | [the Python form of Semantic Versioning](https://packaging.python.org/tutorials/distributing-packages/#choosing-a-versioning-scheme) 13 | (i.e. MAJOR.MINOR.PATCH), 14 | which is almost identical 15 | to [regular semantic versioning](http://semver.org/), but there's no hyphen, e.g. 16 | 17 | - `0.9.0` for a typical final release 18 | - `4.5.2a1` not `4.5.2-a1` for the first Alpha release 19 | - `3.4.5rc2` not `3.4.5-rc2` for Release Candidate 2 20 | 21 | **Note 1:** For Git tags (which are used to identify releases on GitHub), we append a `v` in front. For example, the Git tag for version `2.0.0a1` was `v2.0.0a1`. 22 | 23 | We use `0.9` and `0.9.0` as example version and short-version values below. You should replace those with the correct values for your new version. 24 | 25 | We follow [BEP-1](https://github.com/bigchaindb/BEPs/tree/master/1), which is our variant of C4, the Collective Code Construction Contract, so a release is just a [tagged commit](https://git-scm.com/book/en/v2/Git-Basics-Tagging) on the `master` branch, i.e. a label for a particular Git commit. 26 | 27 | The following steps are what we do to release a new version of _BigchainDB Python Driver_. 28 | 29 | ## Steps 30 | 31 | 1. Create a pull request where you make the following changes: 32 | 33 | - Update `CHANGELOG.md` 34 | - In `bigchaindb-driver/__init__.py`: 35 | - update `__version__` to e.g. `0.9.0` (with no `.dev` on the end) 36 | - In `setup.py` update the version and _maybe_ update the development status item in the `classifiers` list. For example, one allowed value is `"Development Status :: 5 - Production/Stable"`. The [allowed values are listed at pypi.python.org](https://pypi.python.org/pypi?%3Aaction=list_classifiers). 37 | - In `README.rst` update the `Compatibility Matrix` 38 | 39 | 1. **Wait for all the tests to pass!** 40 | 1. Merge the pull request into the `master` branch. 41 | 1. Go to the [bigchaindb/bigchaindb-driver Releases page on GitHub](https://github.com/bigchaindb/bigchaindb-driver/releases) 42 | and click the "Draft a new release" button. 43 | 1. Fill in the details: 44 | - **Tag version:** version number preceded by `v`, e.g. `v0.9.1` 45 | - **Target:** the last commit that was just merged. In other words, that commit will get a Git tag with the value given for tag version above. 46 | - **Title:** Same as tag version above, e.g `v0.9.1` 47 | - **Description:** The body of the changelog entry (Added, Changed, etc.) 48 | 1. Click "Publish release" to publish the release on GitHub. 49 | 1. On your local computer, make sure you're on the `master` branch and that it's up-to-date with the `master` branch in the bigchaindb/bigchaindb-driver repository (e.g. `git pull upstream`). We're going to use that to push a new `bigchaindb-driver` package to PyPI. 50 | 1. Make sure you have a `~/.pypirc` file containing credentials for PyPI or just enter them manually. 51 | 1. Do `make release` to build and publish the new `bigchaindb-driver` package on PyPI. 52 | For this step you need to have `twine` installed. 53 | If you get an error like `Makefile:116: recipe for target 'clean-pyc' failed` 54 | then try doing 55 | ``` 56 | sudo chown -R $(whoami):$(whoami) . 57 | ``` 58 | 1. [Log in to readthedocs.org](https://readthedocs.org/accounts/login/) and go to the **BigchainDB Python Driver** project, then: 59 | - Click on "Builds", select "latest" from the drop-down menu, then click the "Build Version:" button. 60 | - Wait for the build of "latest" to finish. This can take a few minutes. 61 | - Go to Admin --> Advanced Settings 62 | and make sure that "Default branch:" (i.e. what "latest" points to) 63 | is set to the new release's tag, e.g. `v0.9.1`. 64 | (It won't be an option if you didn't wait for the build of "latest" to finish.) 65 | Then scroll to the bottom and click "Save". 66 | - Go to Admin --> Versions 67 | and under **Choose Active Versions**, do these things: 68 | 1. Make sure that the new version's tag is "Active" and "Public" 69 | 1. Make sure the **stable** branch is _not_ active. 70 | 1. Scroll to the bottom of the page and click "Save". 71 | 72 | Congratulations, you have released a new version of BigchainDB Python Driver! 73 | 74 | ## Post-Release Steps 75 | Update the BigchainDB Python Driver version in the `acceptance/python/Dockerfile` in the [BigchainDB Server](https://github.com/bigchaindb/bigchaindb). 76 | -------------------------------------------------------------------------------- /bigchaindb_driver/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright BigchainDB GmbH and BigchainDB contributors 2 | # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) 3 | # Code is Apache-2.0 and docs are CC-BY-4.0 4 | 5 | from .driver import BigchainDB # noqa 6 | 7 | 8 | __author__ = 'BigchainDB' 9 | __email__ = 'devs@bigchaindb.com' 10 | __version__ = '0.6.2' 11 | -------------------------------------------------------------------------------- /bigchaindb_driver/common/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright BigchainDB GmbH and BigchainDB contributors 2 | # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) 3 | # Code is Apache-2.0 and docs are CC-BY-4.0 4 | 5 | ############################################################################### 6 | # DO NOT CHANGE THIS FILE. # 7 | # # 8 | # This is a copy of the `bigchaindb.common` module, any change you want to do # 9 | # here should be done in the original module, located in the BigchainDB # 10 | # repository at . # 11 | # # 12 | # We decided to copy the module here to avoid having the whole BigchainDB # 13 | # package as a dependency. This is a temporary solution until BEP-9 is # 14 | # implemented. # 15 | ############################################################################### 16 | -------------------------------------------------------------------------------- /bigchaindb_driver/common/crypto.py: -------------------------------------------------------------------------------- 1 | # Copyright BigchainDB GmbH and BigchainDB contributors 2 | # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) 3 | # Code is Apache-2.0 and docs are CC-BY-4.0 4 | 5 | ############################################################################### 6 | # DO NOT CHANGE THIS FILE. # 7 | # # 8 | # This is a copy of the `bigchaindb.common` module, any change you want to do # 9 | # here should be done in the original module, located in the BigchainDB # 10 | # repository at . # 11 | # # 12 | # We decided to copy the module here to avoid having the whole BigchainDB # 13 | # package as a dependency. This is a temporary solution until BEP-9 is # 14 | # implemented. # 15 | ############################################################################### 16 | 17 | # Separate all crypto code so that we can easily test several implementations 18 | from collections import namedtuple 19 | 20 | import sha3 21 | from cryptoconditions import crypto 22 | 23 | 24 | CryptoKeypair = namedtuple('CryptoKeypair', ('private_key', 'public_key')) 25 | 26 | 27 | def hash_data(data): 28 | """Hash the provided data using SHA3-256""" 29 | return sha3.sha3_256(data.encode()).hexdigest() 30 | 31 | 32 | def generate_key_pair(): 33 | """Generates a cryptographic key pair. 34 | 35 | Returns: 36 | :class:`~bigchaindb.common.crypto.CryptoKeypair`: A 37 | :obj:`collections.namedtuple` with named fields 38 | :attr:`~bigchaindb.common.crypto.CryptoKeypair.private_key` and 39 | :attr:`~bigchaindb.common.crypto.CryptoKeypair.public_key`. 40 | 41 | """ 42 | # TODO FOR CC: Adjust interface so that this function becomes unnecessary 43 | return CryptoKeypair( 44 | *(k.decode() for k in crypto.ed25519_generate_key_pair())) 45 | 46 | 47 | PrivateKey = crypto.Ed25519SigningKey 48 | PublicKey = crypto.Ed25519VerifyingKey 49 | -------------------------------------------------------------------------------- /bigchaindb_driver/common/exceptions.py: -------------------------------------------------------------------------------- 1 | # Copyright BigchainDB GmbH and BigchainDB contributors 2 | # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) 3 | # Code is Apache-2.0 and docs are CC-BY-4.0 4 | 5 | ############################################################################### 6 | # DO NOT CHANGE THIS FILE. # 7 | # # 8 | # This is a copy of the `bigchaindb.common` module, any change you want to do # 9 | # here should be done in the original module, located in the BigchainDB # 10 | # repository at . # 11 | # # 12 | # We decided to copy the module here to avoid having the whole BigchainDB # 13 | # package as a dependency. This is a temporary solution until BEP-9 is # 14 | # implemented. # 15 | ############################################################################### 16 | 17 | """Custom exceptions used in the `bigchaindb` package. 18 | """ 19 | 20 | 21 | class BigchainDBError(Exception): 22 | """Base class for BigchainDB exceptions.""" 23 | 24 | 25 | class ConfigurationError(BigchainDBError): 26 | """Raised when there is a problem with server configuration""" 27 | 28 | 29 | class DatabaseAlreadyExists(BigchainDBError): 30 | """Raised when trying to create the database but the db is already there""" 31 | 32 | 33 | class DatabaseDoesNotExist(BigchainDBError): 34 | """Raised when trying to delete the database but the db is not there""" 35 | 36 | 37 | class StartupError(BigchainDBError): 38 | """Raised when there is an error starting up the system""" 39 | 40 | 41 | class CyclicBlockchainError(BigchainDBError): 42 | """Raised when there is a cycle in the blockchain""" 43 | 44 | 45 | class KeypairMismatchException(BigchainDBError): 46 | """Raised if the private key(s) provided for signing don't match any of the 47 | current owner(s) 48 | """ 49 | 50 | 51 | class OperationError(BigchainDBError): 52 | """Raised when an operation cannot go through""" 53 | 54 | 55 | ############################################################################## 56 | # Validation errors 57 | # 58 | # All validation errors (which are handleable errors, not faults) should 59 | # subclass ValidationError. However, where possible they should also have their 60 | # own distinct type to differentiate them from other validation errors, 61 | # especially for the purposes of testing. 62 | 63 | 64 | class ValidationError(BigchainDBError): 65 | """Raised if there was an error in validation""" 66 | 67 | 68 | class DoubleSpend(ValidationError): 69 | """Raised if a double spend is found""" 70 | 71 | 72 | class InvalidHash(ValidationError): 73 | """Raised if there was an error checking the hash for a particular 74 | operation 75 | """ 76 | 77 | 78 | class SchemaValidationError(ValidationError): 79 | """Raised if there was any error validating an object's schema""" 80 | 81 | 82 | class InvalidSignature(ValidationError): 83 | """Raised if there was an error checking the signature for a particular 84 | operation 85 | """ 86 | 87 | 88 | class TransactionNotInValidBlock(ValidationError): 89 | """Raised when a transfer transaction is attempting to fulfill the 90 | outputs of a transaction that is in an invalid or undecided block 91 | """ 92 | 93 | 94 | class AssetIdMismatch(ValidationError): 95 | """Raised when multiple transaction inputs related to different assets""" 96 | 97 | 98 | class AmountError(ValidationError): 99 | """Raised when there is a problem with a transaction's output amounts""" 100 | 101 | 102 | class InputDoesNotExist(ValidationError): 103 | """Raised if a transaction input does not exist""" 104 | 105 | 106 | class TransactionOwnerError(ValidationError): 107 | """Raised if a user tries to transfer a transaction they don't own""" 108 | 109 | 110 | class DuplicateTransaction(ValidationError): 111 | """Raised if a duplicated transaction is found""" 112 | 113 | 114 | class ThresholdTooDeep(ValidationError): 115 | """Raised if threshold condition is too deep""" 116 | 117 | 118 | class GenesisBlockAlreadyExistsError(ValidationError): 119 | """Raised when trying to create the already existing genesis block""" 120 | 121 | 122 | class MultipleValidatorOperationError(ValidationError): 123 | """Raised when a validator update pending but new request is submited""" 124 | -------------------------------------------------------------------------------- /bigchaindb_driver/common/utils.py: -------------------------------------------------------------------------------- 1 | # Copyright BigchainDB GmbH and BigchainDB contributors 2 | # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) 3 | # Code is Apache-2.0 and docs are CC-BY-4.0 4 | 5 | ############################################################################### 6 | # DO NOT CHANGE THIS FILE. # 7 | # # 8 | # This is a copy of the `bigchaindb.common` module, any change you want to do # 9 | # here should be done in the original module, located in the BigchainDB # 10 | # repository at . # 11 | # # 12 | # We decided to copy the module here to avoid having the whole BigchainDB # 13 | # package as a dependency. This is a temporary solution until BEP-9 is # 14 | # implemented. # 15 | ############################################################################### 16 | 17 | import time 18 | import re 19 | import rapidjson 20 | 21 | from .exceptions import ValidationError 22 | 23 | 24 | def gen_timestamp(): 25 | """The Unix time, rounded to the nearest second. 26 | See https://en.wikipedia.org/wiki/Unix_time 27 | 28 | Returns: 29 | str: the Unix time 30 | """ 31 | return str(round(time.time())) 32 | 33 | 34 | def serialize(data): 35 | """Serialize a dict into a JSON formatted string. 36 | 37 | This function enforces rules like the separator and order of keys. 38 | This ensures that all dicts are serialized in the same way. 39 | 40 | This is specially important for hashing data. We need to make sure that 41 | everyone serializes their data in the same way so that we do not have 42 | hash mismatches for the same structure due to serialization 43 | differences. 44 | 45 | Args: 46 | data (dict): dict to serialize 47 | 48 | Returns: 49 | str: JSON formatted string 50 | 51 | """ 52 | return rapidjson.dumps(data, skipkeys=False, ensure_ascii=False, 53 | sort_keys=True) 54 | 55 | 56 | def deserialize(data): 57 | """Deserialize a JSON formatted string into a dict. 58 | 59 | Args: 60 | data (str): JSON formatted string. 61 | 62 | Returns: 63 | dict: dict resulting from the serialization of a JSON formatted 64 | string. 65 | """ 66 | return rapidjson.loads(data) 67 | 68 | 69 | def validate_txn_obj(obj_name, obj, key, validation_fun): 70 | """Validate value of `key` in `obj` using `validation_fun`. 71 | 72 | Args: 73 | obj_name (str): name for `obj` being validated. 74 | obj (dict): dictionary object. 75 | key (str): key to be validated in `obj`. 76 | validation_fun (function): function used to validate the value 77 | of `key`. 78 | 79 | Returns: 80 | None: indicates validation successful 81 | 82 | Raises: 83 | ValidationError: `validation_fun` will raise exception on failure 84 | """ 85 | raise NotImplementedError() 86 | 87 | 88 | def validate_all_keys(obj_name, obj, validation_fun): 89 | """Validate all (nested) keys in `obj` by using `validation_fun`. 90 | 91 | Args: 92 | obj_name (str): name for `obj` being validated. 93 | obj (dict): dictionary object. 94 | validation_fun (function): function used to validate the value 95 | of `key`. 96 | 97 | Returns: 98 | None: indicates validation successful 99 | 100 | Raises: 101 | ValidationError: `validation_fun` will raise this error on failure 102 | """ 103 | for key, value in obj.items(): 104 | validation_fun(obj_name, key) 105 | if isinstance(value, dict): 106 | validate_all_keys(obj_name, value, validation_fun) 107 | 108 | 109 | def validate_all_values_for_key(obj, key, validation_fun): 110 | """Validate value for all (nested) occurrence of `key` in `obj` 111 | using `validation_fun`. 112 | 113 | Args: 114 | obj (dict): dictionary object. 115 | key (str): key whose value is to be validated. 116 | validation_fun (function): function used to validate the value 117 | of `key`. 118 | 119 | Raises: 120 | ValidationError: `validation_fun` will raise this error on failure 121 | """ 122 | for vkey, value in obj.items(): 123 | if vkey == key: 124 | validation_fun(value) 125 | elif isinstance(value, dict): 126 | validate_all_values_for_key(value, key, validation_fun) 127 | 128 | 129 | def validate_key(obj_name, key): 130 | """Check if `key` contains ".", "$" or null characters. 131 | 132 | https://docs.mongodb.com/manual/reference/limits/#Restrictions-on-Field-Names 133 | 134 | Args: 135 | obj_name (str): object name to use when raising exception 136 | key (str): key to validated 137 | 138 | Returns: 139 | None: validation successful 140 | 141 | Raises: 142 | ValidationError: will raise exception in case of regex match. 143 | """ 144 | if re.search(r'^[$]|\.|\x00', key): 145 | error_str = ('Invalid key name "{}" in {} object. The ' 146 | 'key name cannot contain characters ' 147 | '".", "$" or null characters').format(key, obj_name) 148 | raise ValidationError(error_str) 149 | -------------------------------------------------------------------------------- /bigchaindb_driver/connection.py: -------------------------------------------------------------------------------- 1 | # Copyright BigchainDB GmbH and BigchainDB contributors 2 | # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) 3 | # Code is Apache-2.0 and docs are CC-BY-4.0 4 | 5 | import time 6 | 7 | from collections import namedtuple 8 | from datetime import datetime, timedelta 9 | 10 | from requests import Session 11 | from requests.exceptions import ConnectionError 12 | 13 | from .exceptions import HTTP_EXCEPTIONS, TransportError 14 | 15 | 16 | BACKOFF_DELAY = 0.5 # seconds 17 | 18 | HttpResponse = namedtuple('HttpResponse', ('status_code', 'headers', 'data')) 19 | 20 | 21 | class Connection: 22 | """A Connection object to make HTTP requests to a particular node.""" 23 | 24 | def __init__(self, *, node_url, headers=None): 25 | """Initializes a :class:`~bigchaindb_driver.connection.Connection` 26 | instance. 27 | 28 | Args: 29 | node_url (str): Url of the node to connect to. 30 | headers (dict): Optional headers to send with each request. 31 | 32 | """ 33 | self.node_url = node_url 34 | self.session = Session() 35 | if headers: 36 | self.session.headers.update(headers) 37 | 38 | self._retries = 0 39 | self.backoff_time = None 40 | 41 | def request(self, method, *, path=None, json=None, 42 | params=None, headers=None, timeout=None, 43 | backoff_cap=None, **kwargs): 44 | """Performs an HTTP request with the given parameters. 45 | 46 | Implements exponential backoff. 47 | 48 | If `ConnectionError` occurs, a timestamp equal to now + 49 | the default delay (`BACKOFF_DELAY`) is assigned to the object. 50 | The timestamp is in UTC. Next time the function is called, it either 51 | waits till the timestamp is passed or raises `TimeoutError`. 52 | 53 | If `ConnectionError` occurs two or more times in a row, 54 | the retry count is incremented and the new timestamp is calculated 55 | as now + the default delay multiplied by two to the power of the 56 | number of retries. 57 | 58 | If a request is successful, the backoff timestamp is removed, 59 | the retry count is back to zero. 60 | 61 | Args: 62 | method (str): HTTP method (e.g.: ``'GET'``). 63 | path (str): API endpoint path (e.g.: ``'/transactions'``). 64 | json (dict): JSON data to send along with the request. 65 | params (dict): Dictionary of URL (query) parameters. 66 | headers (dict): Optional headers to pass to the request. 67 | timeout (int): Optional timeout in seconds. 68 | backoff_cap (int): The maximal allowed backoff delay in seconds 69 | to be assigned to a node. 70 | kwargs: Optional keyword arguments. 71 | 72 | """ 73 | backoff_timedelta = self.get_backoff_timedelta() 74 | 75 | if timeout is not None and timeout < backoff_timedelta: 76 | raise TimeoutError 77 | 78 | if backoff_timedelta > 0: 79 | time.sleep(backoff_timedelta) 80 | 81 | connExc = None 82 | timeout = timeout if timeout is None else timeout - backoff_timedelta 83 | try: 84 | response = self._request( 85 | method=method, 86 | timeout=timeout, 87 | url=self.node_url + path if path else self.node_url, 88 | json=json, 89 | params=params, 90 | headers=headers, 91 | **kwargs, 92 | ) 93 | except ConnectionError as err: 94 | connExc = err 95 | raise err 96 | finally: 97 | self.update_backoff_time(success=connExc is None, 98 | backoff_cap=backoff_cap) 99 | return response 100 | 101 | def get_backoff_timedelta(self): 102 | if self.backoff_time is None: 103 | return 0 104 | 105 | return (self.backoff_time - datetime.utcnow()).total_seconds() 106 | 107 | def update_backoff_time(self, success, backoff_cap=None): 108 | if success: 109 | self._retries = 0 110 | self.backoff_time = None 111 | else: 112 | utcnow = datetime.utcnow() 113 | backoff_delta = BACKOFF_DELAY * 2 ** self._retries 114 | if backoff_cap is not None: 115 | backoff_delta = min(backoff_delta, backoff_cap) 116 | self.backoff_time = utcnow + timedelta(seconds=backoff_delta) 117 | self._retries += 1 118 | 119 | def _request(self, **kwargs): 120 | response = self.session.request(**kwargs) 121 | text = response.text 122 | try: 123 | json = response.json() 124 | except ValueError: 125 | json = None 126 | if not (200 <= response.status_code < 300): 127 | exc_cls = HTTP_EXCEPTIONS.get(response.status_code, TransportError) 128 | raise exc_cls(response.status_code, text, json, kwargs['url']) 129 | data = json if json is not None else text 130 | return HttpResponse(response.status_code, response.headers, data) 131 | -------------------------------------------------------------------------------- /bigchaindb_driver/crypto.py: -------------------------------------------------------------------------------- 1 | # Copyright BigchainDB GmbH and BigchainDB contributors 2 | # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) 3 | # Code is Apache-2.0 and docs are CC-BY-4.0 4 | 5 | from collections import namedtuple 6 | 7 | from cryptoconditions import crypto 8 | 9 | 10 | CryptoKeypair = namedtuple('CryptoKeypair', ('private_key', 'public_key')) 11 | 12 | 13 | def generate_keypair(seed=None): 14 | """Generates a cryptographic key pair. 15 | 16 | Args: 17 | seed (bytes): 32-byte seed for deterministic generation. 18 | Defaults to `None`. 19 | Returns: 20 | :class:`~bigchaindb_driver.crypto.CryptoKeypair`: A 21 | :obj:`collections.namedtuple` with named fields 22 | :attr:`~bigchaindb_driver.crypto.CryptoKeypair.private_key` and 23 | :attr:`~bigchaindb_driver.crypto.CryptoKeypair.public_key`. 24 | 25 | """ 26 | return CryptoKeypair( 27 | *(k.decode() for k in crypto.ed25519_generate_key_pair(seed))) 28 | -------------------------------------------------------------------------------- /bigchaindb_driver/driver.py: -------------------------------------------------------------------------------- 1 | # Copyright BigchainDB GmbH and BigchainDB contributors 2 | # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) 3 | # Code is Apache-2.0 and docs are CC-BY-4.0 4 | 5 | from .transport import Transport 6 | from .offchain import prepare_transaction, fulfill_transaction 7 | from .utils import normalize_nodes 8 | 9 | 10 | class BigchainDB: 11 | """A :class:`~bigchaindb_driver.BigchainDB` driver is able to create, sign, 12 | and submit transactions to one or more nodes in a Federation. 13 | 14 | If initialized with ``>1`` nodes, the driver will send successive 15 | requests to different nodes in a round-robin fashion (this will be 16 | customizable in the future). 17 | 18 | """ 19 | 20 | def __init__(self, *nodes, transport_class=Transport, 21 | headers=None, timeout=20): 22 | """Initialize a :class:`~bigchaindb_driver.BigchainDB` driver instance. 23 | 24 | Args: 25 | *nodes (list of (str or dict)): BigchainDB nodes to connect to. 26 | Currently, the full URL must be given. In the absence of any 27 | node, the default(``'http://localhost:9984'``) will be used. 28 | If node is passed as a dict, `endpoint` is a required key; 29 | `headers` is an optional `dict` of headers. 30 | transport_class: Optional transport class to use. 31 | Defaults to :class:`~bigchaindb_driver.transport.Transport`. 32 | headers (dict): Optional headers that will be passed with 33 | each request. To pass headers only on a per-request 34 | basis, you can pass the headers to the method of choice 35 | (e.g. :meth:`BigchainDB().transactions.send_commit() 36 | <.TransactionsEndpoint.send_commit>`). 37 | timeout (int): Optional timeout in seconds that will be passed 38 | to each request. 39 | """ 40 | self._nodes = normalize_nodes(*nodes, headers=headers) 41 | self._transport = transport_class(*self._nodes, timeout=timeout) 42 | self._transactions = TransactionsEndpoint(self) 43 | self._outputs = OutputsEndpoint(self) 44 | self._blocks = BlocksEndpoint(self) 45 | self._assets = AssetsEndpoint(self) 46 | self._metadata = MetadataEndpoint(self) 47 | self.api_prefix = '/api/v1' 48 | 49 | @property 50 | def nodes(self): 51 | """:obj:`tuple` of :obj:`str`: URLs of connected nodes.""" 52 | return self._nodes 53 | 54 | @property 55 | def transport(self): 56 | """:class:`~bigchaindb_driver.transport.Transport`: Object 57 | responsible for forwarding requests to a 58 | :class:`~bigchaindb_driver.connection.Connection` instance (node). 59 | """ 60 | return self._transport 61 | 62 | @property 63 | def transactions(self): 64 | """:class:`~bigchaindb_driver.driver.TransactionsEndpoint`: 65 | Exposes functionalities of the ``'/transactions'`` endpoint. 66 | """ 67 | return self._transactions 68 | 69 | @property 70 | def outputs(self): 71 | """:class:`~bigchaindb_driver.driver.OutputsEndpoint`: 72 | Exposes functionalities of the ``'/outputs'`` endpoint. 73 | """ 74 | return self._outputs 75 | 76 | @property 77 | def assets(self): 78 | """:class:`~bigchaindb_driver.driver.AssetsEndpoint`: 79 | Exposes functionalities of the ``'/assets'`` endpoint. 80 | """ 81 | return self._assets 82 | 83 | @property 84 | def metadata(self): 85 | """:class:`~bigchaindb_driver.driver.MetadataEndpoint`: 86 | Exposes functionalities of the ``'/metadata'`` endpoint. 87 | """ 88 | return self._metadata 89 | 90 | @property 91 | def blocks(self): 92 | """:class:`~bigchaindb_driver.driver.BlocksEndpoint`: 93 | Exposes functionalities of the ``'/blocks'`` endpoint. 94 | """ 95 | return self._blocks 96 | 97 | def info(self, headers=None): 98 | """Retrieves information of the node being connected to via the 99 | root endpoint ``'/'``. 100 | 101 | Args: 102 | headers (dict): Optional headers to pass to the request. 103 | 104 | Returns: 105 | dict: Details of the node that this instance is connected 106 | to. Some information that may be interesting: 107 | 108 | * the server version and 109 | * an overview of all the endpoints 110 | 111 | Note: 112 | Currently limited to one node, and will be expanded to 113 | return information for each node that this instance is 114 | connected to. 115 | 116 | """ 117 | return self.transport.forward_request( 118 | method='GET', path='/', headers=headers) 119 | 120 | def api_info(self, headers=None): 121 | """Retrieves information provided by the API root endpoint 122 | ``'/api/v1'``. 123 | 124 | Args: 125 | headers (dict): Optional headers to pass to the request. 126 | 127 | Returns: 128 | dict: Details of the HTTP API provided by the BigchainDB 129 | server. 130 | 131 | """ 132 | return self.transport.forward_request( 133 | method='GET', 134 | path=self.api_prefix, 135 | headers=headers, 136 | ) 137 | 138 | 139 | class NamespacedDriver: 140 | """Base class for creating endpoints (namespaced objects) that can be added 141 | under the :class:`~bigchaindb_driver.driver.BigchainDB` driver. 142 | """ 143 | 144 | PATH = '/' 145 | 146 | def __init__(self, driver): 147 | """Initializes an instance of 148 | :class:`~bigchaindb_driver.driver.NamespacedDriver` with the given 149 | driver instance. 150 | 151 | Args: 152 | driver (BigchainDB): Instance of 153 | :class:`~bigchaindb_driver.driver.BigchainDB`. 154 | """ 155 | self.driver = driver 156 | 157 | @property 158 | def transport(self): 159 | return self.driver.transport 160 | 161 | @property 162 | def api_prefix(self): 163 | return self.driver.api_prefix 164 | 165 | @property 166 | def path(self): 167 | return self.api_prefix + self.PATH 168 | 169 | 170 | class TransactionsEndpoint(NamespacedDriver): 171 | """Exposes functionality of the ``'/transactions/'`` endpoint. 172 | 173 | Attributes: 174 | path (str): The path of the endpoint. 175 | 176 | """ 177 | 178 | PATH = '/transactions/' 179 | 180 | @staticmethod 181 | def prepare(*, operation='CREATE', signers=None, 182 | recipients=None, asset=None, metadata=None, inputs=None): 183 | """Prepares a transaction payload, ready to be fulfilled. 184 | 185 | Args: 186 | operation (str): The operation to perform. Must be ``'CREATE'`` 187 | or ``'TRANSFER'``. Case insensitive. Defaults to ``'CREATE'``. 188 | signers (:obj:`list` | :obj:`tuple` | :obj:`str`, optional): 189 | One or more public keys representing the issuer(s) of 190 | the asset being created. Only applies for ``'CREATE'`` 191 | operations. Defaults to ``None``. 192 | recipients (:obj:`list` | :obj:`tuple` | :obj:`str`, optional): 193 | One or more public keys representing the new recipients(s) 194 | of the asset being created or transferred. 195 | Defaults to ``None``. 196 | asset (:obj:`dict`, optional): The asset to be created or 197 | transferred. MUST be supplied for ``'TRANSFER'`` operations. 198 | Defaults to ``None``. 199 | metadata (:obj:`dict`, optional): Metadata associated with the 200 | transaction. Defaults to ``None``. 201 | inputs (:obj:`dict` | :obj:`list` | :obj:`tuple`, optional): 202 | One or more inputs holding the condition(s) that this 203 | transaction intends to fulfill. Each input is expected to 204 | be a :obj:`dict`. Only applies to, and MUST be supplied for, 205 | ``'TRANSFER'`` operations. 206 | 207 | Returns: 208 | dict: The prepared transaction. 209 | 210 | Raises: 211 | :class:`~.exceptions.BigchaindbException`: If ``operation`` is 212 | not ``'CREATE'`` or ``'TRANSFER'``. 213 | 214 | .. important:: 215 | 216 | **CREATE operations** 217 | 218 | * ``signers`` MUST be set. 219 | * ``recipients``, ``asset``, and ``metadata`` MAY be set. 220 | * If ``asset`` is set, it MUST be in the form of:: 221 | 222 | { 223 | 'data': { 224 | ... 225 | } 226 | } 227 | 228 | * The argument ``inputs`` is ignored. 229 | * If ``recipients`` is not given, or evaluates to 230 | ``False``, it will be set equal to ``signers``:: 231 | 232 | if not recipients: 233 | recipients = signers 234 | 235 | **TRANSFER operations** 236 | 237 | * ``recipients``, ``asset``, and ``inputs`` MUST be set. 238 | * ``asset`` MUST be in the form of:: 239 | 240 | { 241 | 'id': '' 242 | } 243 | 244 | * ``metadata`` MAY be set. 245 | * The argument ``signers`` is ignored. 246 | 247 | """ 248 | return prepare_transaction( 249 | operation=operation, 250 | signers=signers, 251 | recipients=recipients, 252 | asset=asset, 253 | metadata=metadata, 254 | inputs=inputs, 255 | ) 256 | 257 | @staticmethod 258 | def fulfill(transaction, private_keys): 259 | """Fulfills the given transaction. 260 | 261 | Args: 262 | transaction (dict): The transaction to be fulfilled. 263 | private_keys (:obj:`str` | :obj:`list` | :obj:`tuple`): One or 264 | more private keys to be used for fulfilling the 265 | transaction. 266 | 267 | Returns: 268 | dict: The fulfilled transaction payload, ready to be sent to a 269 | BigchainDB federation. 270 | 271 | Raises: 272 | :exc:`~.exceptions.MissingPrivateKeyError`: If a private 273 | key is missing. 274 | 275 | """ 276 | return fulfill_transaction(transaction, private_keys=private_keys) 277 | 278 | def get(self, *, asset_id, operation=None, headers=None): 279 | """Given an asset id, get its list of transactions (and 280 | optionally filter for only ``'CREATE'`` or ``'TRANSFER'`` 281 | transactions). 282 | 283 | Args: 284 | asset_id (str): Id of the asset. 285 | operation (str): The type of operation the transaction 286 | should be. Either ``'CREATE'`` or ``'TRANSFER'``. 287 | Defaults to ``None``. 288 | headers (dict): Optional headers to pass to the request. 289 | 290 | Note: 291 | Please note that the id of an asset in BigchainDB is 292 | actually the id of the transaction which created the asset. 293 | In other words, when querying for an asset id with the 294 | operation set to ``'CREATE'``, only one transaction should 295 | be expected. This transaction will be the transaction in 296 | which the asset was created, and the transaction id will be 297 | equal to the given asset id. Hence, the following calls to 298 | :meth:`.retrieve` and :meth:`.get` should return the same 299 | transaction. 300 | 301 | >>> bdb = BigchainDB() 302 | >>> bdb.transactions.retrieve('foo') 303 | >>> bdb.transactions.get(asset_id='foo', operation='CREATE') 304 | 305 | Since :meth:`.get` returns a list of transactions, it may 306 | be more efficient to use :meth:`.retrieve` instead, if one 307 | is only interested in the ``'CREATE'`` operation. 308 | 309 | Returns: 310 | list: List of transactions. 311 | 312 | """ 313 | return self.transport.forward_request( 314 | method='GET', 315 | path=self.path, 316 | params={'asset_id': asset_id, 'operation': operation}, 317 | headers=headers, 318 | ) 319 | 320 | def send_async(self, transaction, headers=None): 321 | """Submit a transaction to the Federation with the mode `async`. 322 | 323 | Args: 324 | transaction (dict): the transaction to be sent 325 | to the Federation node(s). 326 | headers (dict): Optional headers to pass to the request. 327 | 328 | Returns: 329 | dict: The transaction sent to the Federation node(s). 330 | 331 | """ 332 | return self.transport.forward_request( 333 | method='POST', 334 | path=self.path, 335 | json=transaction, 336 | params={'mode': 'async'}, 337 | headers=headers) 338 | 339 | def send_sync(self, transaction, headers=None): 340 | """Submit a transaction to the Federation with the mode `sync`. 341 | 342 | Args: 343 | transaction (dict): the transaction to be sent 344 | to the Federation node(s). 345 | headers (dict): Optional headers to pass to the request. 346 | 347 | Returns: 348 | dict: The transaction sent to the Federation node(s). 349 | 350 | """ 351 | return self.transport.forward_request( 352 | method='POST', 353 | path=self.path, 354 | json=transaction, 355 | params={'mode': 'sync'}, 356 | headers=headers) 357 | 358 | def send_commit(self, transaction, headers=None): 359 | """Submit a transaction to the Federation with the mode `commit`. 360 | 361 | Args: 362 | transaction (dict): the transaction to be sent 363 | to the Federation node(s). 364 | headers (dict): Optional headers to pass to the request. 365 | 366 | Returns: 367 | dict: The transaction sent to the Federation node(s). 368 | 369 | """ 370 | return self.transport.forward_request( 371 | method='POST', 372 | path=self.path, 373 | json=transaction, 374 | params={'mode': 'commit'}, 375 | headers=headers) 376 | 377 | def retrieve(self, txid, headers=None): 378 | """Retrieves the transaction with the given id. 379 | 380 | Args: 381 | txid (str): Id of the transaction to retrieve. 382 | headers (dict): Optional headers to pass to the request. 383 | 384 | Returns: 385 | dict: The transaction with the given id. 386 | 387 | """ 388 | path = self.path + txid 389 | return self.transport.forward_request( 390 | method='GET', path=path, headers=None) 391 | 392 | 393 | class OutputsEndpoint(NamespacedDriver): 394 | """Exposes functionality of the ``'/outputs'`` endpoint. 395 | 396 | Attributes: 397 | path (str): The path of the endpoint. 398 | 399 | """ 400 | 401 | PATH = '/outputs/' 402 | 403 | def get(self, public_key, spent=None, headers=None): 404 | """Get transaction outputs by public key. The public_key parameter 405 | must be a base58 encoded ed25519 public key associated with 406 | transaction output ownership. 407 | 408 | Args: 409 | public_key (str): Public key for which unfulfilled 410 | conditions are sought. 411 | spent (bool): Indicate if the result set should include only spent 412 | or only unspent outputs. If not specified (``None``) the 413 | result includes all the outputs (both spent and unspent) 414 | associated with the public key. 415 | headers (dict): Optional headers to pass to the request. 416 | 417 | Returns: 418 | :obj:`list` of :obj:`str`: List of unfulfilled conditions. 419 | 420 | Example: 421 | Given a transaction with `id` ``da1b64a907ba54`` having an 422 | `ed25519` condition (at index ``0``) with alice's public 423 | key:: 424 | 425 | >>> bdb = BigchainDB() 426 | >>> bdb.outputs.get(alice_pubkey) 427 | ... ['../transactions/da1b64a907ba54/conditions/0'] 428 | 429 | """ 430 | return self.transport.forward_request( 431 | method='GET', 432 | path=self.path, 433 | params={'public_key': public_key, 'spent': spent}, 434 | headers=headers, 435 | ) 436 | 437 | 438 | class BlocksEndpoint(NamespacedDriver): 439 | """Exposes functionality of the ``'/blocks'`` endpoint. 440 | 441 | Attributes: 442 | path (str): The path of the endpoint. 443 | 444 | """ 445 | 446 | PATH = '/blocks/' 447 | 448 | def get(self, *, txid, headers=None): 449 | """Get the block that contains the given transaction id (``txid``) 450 | else return ``None`` 451 | 452 | Args: 453 | txid (str): Transaction id. 454 | headers (dict): Optional headers to pass to the request. 455 | 456 | Returns: 457 | :obj:`list` of :obj:`int`: List of block heights. 458 | 459 | """ 460 | block_list = self.transport.forward_request( 461 | method='GET', 462 | path=self.path, 463 | params={'transaction_id': txid}, 464 | headers=headers, 465 | ) 466 | return block_list[0] if len(block_list) else None 467 | 468 | def retrieve(self, block_height, headers=None): 469 | """Retrieves the block with the given ``block_height``. 470 | 471 | Args: 472 | block_height (str): height of the block to retrieve. 473 | headers (dict): Optional headers to pass to the request. 474 | 475 | Returns: 476 | dict: The block with the given ``block_height``. 477 | 478 | """ 479 | path = self.path + block_height 480 | return self.transport.forward_request( 481 | method='GET', path=path, headers=None) 482 | 483 | 484 | class AssetsEndpoint(NamespacedDriver): 485 | """Exposes functionality of the ``'/assets'`` endpoint. 486 | 487 | Attributes: 488 | path (str): The path of the endpoint. 489 | 490 | """ 491 | 492 | PATH = '/assets/' 493 | 494 | def get(self, *, search, limit=0, headers=None): 495 | """Retrieves the assets that match a given text search string. 496 | 497 | Args: 498 | search (str): Text search string. 499 | limit (int): Limit the number of returned documents. Defaults to 500 | zero meaning that it returns all the matching assets. 501 | headers (dict): Optional headers to pass to the request. 502 | 503 | Returns: 504 | :obj:`list` of :obj:`dict`: List of assets that match the query. 505 | 506 | """ 507 | return self.transport.forward_request( 508 | method='GET', 509 | path=self.path, 510 | params={'search': search, 'limit': limit}, 511 | headers=headers 512 | ) 513 | 514 | 515 | class MetadataEndpoint(NamespacedDriver): 516 | """Exposes functionality of the ``'/metadata'`` endpoint. 517 | 518 | Attributes: 519 | path (str): The path of the endpoint. 520 | 521 | """ 522 | 523 | PATH = '/metadata/' 524 | 525 | def get(self, *, search, limit=0, headers=None): 526 | """Retrieves the metadata that match a given text search string. 527 | 528 | Args: 529 | search (str): Text search string. 530 | limit (int): Limit the number of returned documents. Defaults to 531 | zero meaning that it returns all the matching metadata. 532 | headers (dict): Optional headers to pass to the request. 533 | 534 | Returns: 535 | :obj:`list` of :obj:`dict`: List of metadata that match the query. 536 | 537 | """ 538 | return self.transport.forward_request( 539 | method='GET', 540 | path=self.path, 541 | params={'search': search, 'limit': limit}, 542 | headers=headers 543 | ) 544 | -------------------------------------------------------------------------------- /bigchaindb_driver/exceptions.py: -------------------------------------------------------------------------------- 1 | # Copyright BigchainDB GmbH and BigchainDB contributors 2 | # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) 3 | # Code is Apache-2.0 and docs are CC-BY-4.0 4 | 5 | """Exceptions used by :mod:`bigchaindb_driver`.""" 6 | 7 | 8 | class BigchaindbException(Exception): 9 | """Base exception for all Bigchaindb exceptions.""" 10 | 11 | 12 | class KeypairNotFoundException(BigchaindbException): 13 | """Raised if an operation cannot proceed because the keypair 14 | was not given. 15 | """ 16 | 17 | 18 | class InvalidPrivateKey(BigchaindbException): 19 | """Raised if a private key is invalid. E.g.: :obj:`None`.""" 20 | 21 | 22 | class InvalidPublicKey(BigchaindbException): 23 | """Raised if a public key is invalid. E.g.: :obj:`None`.""" 24 | 25 | 26 | class MissingPrivateKeyError(BigchaindbException): 27 | """Raised if a private key is missing.""" 28 | 29 | 30 | class TimeoutError(BigchaindbException): 31 | """Raised if the request algorithm times out.""" 32 | 33 | @property 34 | def connection_errors(self): 35 | """Returns connection errors occurred before timeout expired.""" 36 | return self.args[0] 37 | 38 | 39 | class TransportError(BigchaindbException): 40 | """Base exception for transport related errors. 41 | 42 | This is mainly for cases where the status code denotes an HTTP error, and 43 | for cases in which there was a connection error. 44 | 45 | """ 46 | 47 | @property 48 | def status_code(self): 49 | return self.args[0] 50 | 51 | @property 52 | def error(self): 53 | return self.args[1] 54 | 55 | @property 56 | def info(self): 57 | return self.args[2] 58 | 59 | @property 60 | def url(self): 61 | return self.args[3] 62 | 63 | 64 | class ConnectionError(TransportError): 65 | """Exception for errors occurring when connecting, and/or making a request 66 | to Bigchaindb. 67 | 68 | """ 69 | 70 | 71 | class BadRequest(TransportError): 72 | """Exception for HTTP 400 errors.""" 73 | 74 | 75 | class NotFoundError(TransportError): 76 | """Exception for HTTP 404 errors.""" 77 | 78 | 79 | class ServiceUnavailable(TransportError): 80 | """Exception for HTTP 503 errors.""" 81 | 82 | 83 | class GatewayTimeout(TransportError): 84 | """Exception for HTTP 503 errors.""" 85 | 86 | 87 | HTTP_EXCEPTIONS = { 88 | 400: BadRequest, 89 | 404: NotFoundError, 90 | 503: ServiceUnavailable, 91 | 504: GatewayTimeout, 92 | } 93 | -------------------------------------------------------------------------------- /bigchaindb_driver/offchain.py: -------------------------------------------------------------------------------- 1 | # Copyright BigchainDB GmbH and BigchainDB contributors 2 | # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) 3 | # Code is Apache-2.0 and docs are CC-BY-4.0 4 | 5 | """Module for operations that can be performed "offchain", meaning without 6 | a connection to one or more BigchainDB federation nodes. 7 | 8 | """ 9 | import logging 10 | from functools import singledispatch 11 | 12 | from .common.transaction import ( 13 | Input, 14 | Transaction, 15 | TransactionLink, 16 | _fulfillment_from_details 17 | ) 18 | from .common.exceptions import KeypairMismatchException 19 | 20 | from .exceptions import BigchaindbException, MissingPrivateKeyError 21 | from .utils import ( 22 | CreateOperation, 23 | TransferOperation, 24 | _normalize_operation, 25 | ) 26 | 27 | logger = logging.getLogger(__name__) 28 | 29 | 30 | @singledispatch 31 | def _prepare_transaction(operation, 32 | signers=None, 33 | recipients=None, 34 | asset=None, 35 | metadata=None, 36 | inputs=None): 37 | raise BigchaindbException(( 38 | 'Unsupported operation: {}. ' 39 | 'Only "CREATE" and "TRANSFER" are supported.'.format(operation))) 40 | 41 | 42 | @_prepare_transaction.register(CreateOperation) 43 | def _prepare_create_transaction_dispatcher(operation, **kwargs): 44 | del kwargs['inputs'] 45 | return prepare_create_transaction(**kwargs) 46 | 47 | 48 | @_prepare_transaction.register(TransferOperation) 49 | def _prepare_transfer_transaction_dispatcher(operation, **kwargs): 50 | del kwargs['signers'] 51 | return prepare_transfer_transaction(**kwargs) 52 | 53 | 54 | def prepare_transaction(*, operation='CREATE', signers=None, 55 | recipients=None, asset=None, metadata=None, 56 | inputs=None): 57 | """Prepares a transaction payload, ready to be fulfilled. Depending on 58 | the value of ``operation``, simply dispatches to either 59 | :func:`~.prepare_create_transaction` or 60 | :func:`~.prepare_transfer_transaction`. 61 | 62 | Args: 63 | operation (str): The operation to perform. Must be ``'CREATE'`` 64 | or ``'TRANSFER'``. Case insensitive. Defaults to ``'CREATE'``. 65 | signers (:obj:`list` | :obj:`tuple` | :obj:`str`, optional): 66 | One or more public keys representing the issuer(s) of 67 | the asset being created. Only applies for ``'CREATE'`` 68 | operations. Defaults to ``None``. 69 | recipients (:obj:`list` | :obj:`tuple` | :obj:`str`, optional): 70 | One or more public keys representing the new recipients(s) 71 | of the asset being created or transferred. 72 | Defaults to ``None``. 73 | asset (:obj:`dict`, optional): The asset to be created or 74 | transferred. MUST be supplied for ``'TRANSFER'`` operations. 75 | Defaults to ``None``. 76 | metadata (:obj:`dict`, optional): Metadata associated with the 77 | transaction. Defaults to ``None``. 78 | inputs (:obj:`dict` | :obj:`list` | :obj:`tuple`, optional): 79 | One or more inputs holding the condition(s) that this 80 | transaction intends to fulfill. Each input is expected to 81 | be a :obj:`dict`. Only applies to, and MUST be supplied for, 82 | ``'TRANSFER'`` operations. 83 | 84 | Returns: 85 | dict: The prepared transaction. 86 | 87 | Raises: 88 | :class:`~.exceptions.BigchaindbException`: If ``operation`` is 89 | not ``'CREATE'`` or ``'TRANSFER'``. 90 | 91 | .. important:: 92 | 93 | **CREATE operations** 94 | 95 | * ``signers`` MUST be set. 96 | * ``recipients``, ``asset``, and ``metadata`` MAY be set. 97 | * If ``asset`` is set, it MUST be in the form of:: 98 | 99 | { 100 | 'data': { 101 | ... 102 | } 103 | } 104 | 105 | * The argument ``inputs`` is ignored. 106 | * If ``recipients`` is not given, or evaluates to 107 | ``False``, it will be set equal to ``signers``:: 108 | 109 | if not recipients: 110 | recipients = signers 111 | 112 | **TRANSFER operations** 113 | 114 | * ``recipients``, ``asset``, and ``inputs`` MUST be set. 115 | * ``asset`` MUST be in the form of:: 116 | 117 | { 118 | 'id': '' 119 | } 120 | 121 | * ``metadata`` MAY be set. 122 | * The argument ``signers`` is ignored. 123 | 124 | """ 125 | operation = _normalize_operation(operation) 126 | return _prepare_transaction( 127 | operation, 128 | signers=signers, 129 | recipients=recipients, 130 | asset=asset, 131 | metadata=metadata, 132 | inputs=inputs, 133 | ) 134 | 135 | 136 | def prepare_create_transaction(*, 137 | signers, 138 | recipients=None, 139 | asset=None, 140 | metadata=None): 141 | """Prepares a ``"CREATE"`` transaction payload, ready to be 142 | fulfilled. 143 | 144 | Args: 145 | signers (:obj:`list` | :obj:`tuple` | :obj:`str`): One 146 | or more public keys representing the issuer(s) of the asset 147 | being created. 148 | recipients (:obj:`list` | :obj:`tuple` | :obj:`str`, optional): 149 | One or more public keys representing the new recipients(s) 150 | of the asset being created. Defaults to ``None``. 151 | asset (:obj:`dict`, optional): The asset to be created. 152 | Defaults to ``None``. 153 | metadata (:obj:`dict`, optional): Metadata associated with the 154 | transaction. Defaults to ``None``. 155 | 156 | Returns: 157 | dict: The prepared ``"CREATE"`` transaction. 158 | 159 | .. important:: 160 | 161 | * If ``asset`` is set, it MUST be in the form of:: 162 | 163 | { 164 | 'data': { 165 | ... 166 | } 167 | } 168 | 169 | * If ``recipients`` is not given, or evaluates to 170 | ``False``, it will be set equal to ``signers``:: 171 | 172 | if not recipients: 173 | recipients = signers 174 | 175 | """ 176 | if not isinstance(signers, (list, tuple)): 177 | signers = [signers] 178 | # NOTE: Needed for the time being. See 179 | # https://github.com/bigchaindb/bigchaindb/issues/797 180 | elif isinstance(signers, tuple): 181 | signers = list(signers) 182 | 183 | if not recipients: 184 | recipients = [(signers, 1)] 185 | elif not isinstance(recipients, (list, tuple)): 186 | recipients = [([recipients], 1)] 187 | # NOTE: Needed for the time being. See 188 | # https://github.com/bigchaindb/bigchaindb/issues/797 189 | elif isinstance(recipients, tuple): 190 | recipients = [(list(recipients), 1)] 191 | 192 | transaction = Transaction.create( 193 | signers, 194 | recipients, 195 | metadata=metadata, 196 | asset=asset['data'] if asset else None, 197 | ) 198 | return transaction.to_dict() 199 | 200 | 201 | def prepare_transfer_transaction(*, 202 | inputs, 203 | recipients, 204 | asset, 205 | metadata=None): 206 | """Prepares a ``"TRANSFER"`` transaction payload, ready to be 207 | fulfilled. 208 | 209 | Args: 210 | inputs (:obj:`dict` | :obj:`list` | :obj:`tuple`): One or more 211 | inputs holding the condition(s) that this transaction 212 | intends to fulfill. Each input is expected to be a 213 | :obj:`dict`. 214 | recipients (:obj:`str` | :obj:`list` | :obj:`tuple`): One or 215 | more public keys representing the new recipients(s) of the 216 | asset being transferred. 217 | asset (:obj:`dict`): A single-key dictionary holding the ``id`` 218 | of the asset being transferred with this transaction. 219 | metadata (:obj:`dict`): Metadata associated with the 220 | transaction. Defaults to ``None``. 221 | 222 | Returns: 223 | dict: The prepared ``"TRANSFER"`` transaction. 224 | 225 | .. important:: 226 | 227 | * ``asset`` MUST be in the form of:: 228 | 229 | { 230 | 'id': '' 231 | } 232 | 233 | Example: 234 | 235 | .. todo:: Replace this section with docs. 236 | 237 | In case it may not be clear what an input should look like, say 238 | Alice (public key: ``'3Cxh1eKZk3Wp9KGBWFS7iVde465UvqUKnEqTg2MW4wNf'``) 239 | wishes to transfer an asset over to Bob 240 | (public key: ``'EcRawy3Y22eAUSS94vLF8BVJi62wbqbD9iSUSUNU9wAA'``). 241 | Let the asset creation transaction payload be denoted by 242 | ``tx``:: 243 | 244 | # noqa E501 245 | >>> tx 246 | {'asset': {'data': {'msg': 'Hello BigchainDB!'}}, 247 | 'id': '9650055df2539223586d33d273cb8fd05bd6d485b1fef1caf7c8901a49464c87', 248 | 'inputs': [{'fulfillment': {'public_key': '3Cxh1eKZk3Wp9KGBWFS7iVde465UvqUKnEqTg2MW4wNf', 249 | 'type': 'ed25519-sha-256'}, 250 | 'fulfills': None, 251 | 'owners_before': ['3Cxh1eKZk3Wp9KGBWFS7iVde465UvqUKnEqTg2MW4wNf']}], 252 | 'metadata': None, 253 | 'operation': 'CREATE', 254 | 'outputs': [{'amount': '1', 255 | 'condition': {'details': {'public_key': '3Cxh1eKZk3Wp9KGBWFS7iVde465UvqUKnEqTg2MW4wNf', 256 | 'type': 'ed25519-sha-256'}, 257 | 'uri': 'ni:///sha-256;7ApQLsLLQgj5WOUipJg1txojmge68pctwFxvc3iOl54?fpt=ed25519-sha-256&cost=131072'}, 258 | 'public_keys': ['3Cxh1eKZk3Wp9KGBWFS7iVde465UvqUKnEqTg2MW4wNf']}], 259 | 'version': '2.0'} 260 | 261 | Then, the input may be constructed in this way:: 262 | 263 | output_index 264 | output = tx['transaction']['outputs'][output_index] 265 | input_ = { 266 | 'fulfillment': output['condition']['details'], 267 | 'input': { 268 | 'output_index': output_index, 269 | 'transaction_id': tx['id'], 270 | }, 271 | 'owners_before': output['owners_after'], 272 | } 273 | 274 | Displaying the input on the prompt would look like:: 275 | 276 | >>> input_ 277 | {'fulfillment': { 278 | 'public_key': '3Cxh1eKZk3Wp9KGBWFS7iVde465UvqUKnEqTg2MW4wNf', 279 | 'type': 'ed25519-sha-256'}, 280 | 'input': {'output_index': 0, 281 | 'transaction_id': '9650055df2539223586d33d273cb8fd05bd6d485b1fef1caf7c8901a49464c87'}, 282 | 'owners_before': ['3Cxh1eKZk3Wp9KGBWFS7iVde465UvqUKnEqTg2MW4wNf']} 283 | 284 | 285 | To prepare the transfer: 286 | 287 | >>> prepare_transfer_transaction( 288 | ... inputs=input_, 289 | ... recipients='EcRawy3Y22eAUSS94vLF8BVJi62wbqbD9iSUSUNU9wAA', 290 | ... asset=tx['transaction']['asset'], 291 | ... ) 292 | 293 | """ 294 | if not isinstance(inputs, (list, tuple)): 295 | inputs = (inputs, ) 296 | if not isinstance(recipients, (list, tuple)): 297 | recipients = [([recipients], 1)] 298 | 299 | # NOTE: Needed for the time being. See 300 | # https://github.com/bigchaindb/bigchaindb/issues/797 301 | if isinstance(recipients, tuple): 302 | recipients = [(list(recipients), 1)] 303 | 304 | fulfillments = [ 305 | Input(_fulfillment_from_details(input_['fulfillment']), 306 | input_['owners_before'], 307 | fulfills=TransactionLink( 308 | txid=input_['fulfills']['transaction_id'], 309 | output=input_['fulfills']['output_index'])) 310 | for input_ in inputs 311 | ] 312 | 313 | transaction = Transaction.transfer( 314 | fulfillments, 315 | recipients, 316 | asset_id=asset['id'], 317 | metadata=metadata, 318 | ) 319 | return transaction.to_dict() 320 | 321 | 322 | def fulfill_transaction(transaction, *, private_keys): 323 | """Fulfills the given transaction. 324 | 325 | Args: 326 | transaction (dict): The transaction to be fulfilled. 327 | private_keys (:obj:`str` | :obj:`list` | :obj:`tuple`): One or 328 | more private keys to be used for fulfilling the 329 | transaction. 330 | 331 | Returns: 332 | dict: The fulfilled transaction payload, ready to be sent to a 333 | BigchainDB federation. 334 | 335 | Raises: 336 | :exc:`~.exceptions.MissingPrivateKeyError`: If a private 337 | key is missing. 338 | 339 | """ 340 | if not isinstance(private_keys, (list, tuple)): 341 | private_keys = [private_keys] 342 | 343 | # NOTE: Needed for the time being. See 344 | # https://github.com/bigchaindb/bigchaindb/issues/797 345 | if isinstance(private_keys, tuple): 346 | private_keys = list(private_keys) 347 | 348 | transaction_obj = Transaction.from_dict(transaction) 349 | try: 350 | signed_transaction = transaction_obj.sign(private_keys) 351 | except KeypairMismatchException as exc: 352 | raise MissingPrivateKeyError('A private key is missing!') from exc 353 | 354 | return signed_transaction.to_dict() 355 | -------------------------------------------------------------------------------- /bigchaindb_driver/pool.py: -------------------------------------------------------------------------------- 1 | # Copyright BigchainDB GmbH and BigchainDB contributors 2 | # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) 3 | # Code is Apache-2.0 and docs are CC-BY-4.0 4 | 5 | from abc import ABCMeta, abstractmethod 6 | from datetime import datetime 7 | 8 | 9 | class AbstractPicker(metaclass=ABCMeta): 10 | """Abstract class for picker classes that pick connections from a pool.""" 11 | 12 | @abstractmethod 13 | def pick(self, connections): 14 | """Picks a :class:`~bigchaindb_driver.connection.Connection` 15 | instance from the given list of 16 | :class:`~bigchaindb_driver.connection.Connection` instances. 17 | 18 | Args: 19 | connections (List): List of 20 | :class:`~bigchaindb_driver.connection.Connection` instances. 21 | 22 | """ 23 | pass # pragma: no cover 24 | 25 | 26 | class RoundRobinPicker(AbstractPicker): 27 | """Picks a :class:`~bigchaindb_driver.connection.Connection` 28 | instance from a list of connections. 29 | 30 | """ 31 | 32 | def pick(self, connections): 33 | """Picks a connection with the earliest backoff time. 34 | 35 | As a result, the first connection is picked 36 | for as long as it has no backoff time. 37 | Otherwise, the connections are tried in a round robin fashion. 38 | 39 | Args: 40 | connections (:obj:list): List of 41 | :class:`~bigchaindb_driver.connection.Connection` instances. 42 | 43 | """ 44 | if len(connections) == 1: 45 | return connections[0] 46 | 47 | def key(conn): 48 | return (datetime.min 49 | if conn.backoff_time is None 50 | else conn.backoff_time) 51 | 52 | return min(*connections, key=key) 53 | 54 | 55 | class Pool: 56 | """Pool of connections.""" 57 | 58 | def __init__(self, connections, picker_class=RoundRobinPicker): 59 | """Initializes a :class:`~bigchaindb_driver.pool.Pool` instance. 60 | 61 | Args: 62 | connections (list): List of 63 | :class:`~bigchaindb_driver.connection.Connection` instances. 64 | 65 | """ 66 | self.connections = connections 67 | self.picker = picker_class() 68 | 69 | def get_connection(self): 70 | """Gets a :class:`~bigchaindb_driver.connection.Connection` 71 | instance from the pool. 72 | 73 | Returns: 74 | A :class:`~bigchaindb_driver.connection.Connection` instance. 75 | 76 | """ 77 | return self.picker.pick(self.connections) 78 | -------------------------------------------------------------------------------- /bigchaindb_driver/transport.py: -------------------------------------------------------------------------------- 1 | # Copyright BigchainDB GmbH and BigchainDB contributors 2 | # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) 3 | # Code is Apache-2.0 and docs are CC-BY-4.0 4 | 5 | from time import time 6 | 7 | from requests.exceptions import ConnectionError 8 | 9 | from .connection import Connection 10 | from .exceptions import TimeoutError 11 | from .pool import Pool 12 | 13 | 14 | NO_TIMEOUT_BACKOFF_CAP = 10 # seconds 15 | 16 | 17 | class Transport: 18 | """Transport class. 19 | 20 | """ 21 | 22 | def __init__(self, *nodes, timeout=None): 23 | """Initializes an instance of 24 | :class:`~bigchaindb_driver.transport.Transport`. 25 | 26 | Args: 27 | nodes: each node is a dictionary with the keys `endpoint` and 28 | `headers` 29 | timeout (int): Optional timeout in seconds. 30 | 31 | """ 32 | self.nodes = nodes 33 | self.timeout = timeout 34 | self.connection_pool = Pool([Connection(node_url=node['endpoint'], 35 | headers=node['headers']) 36 | for node in nodes]) 37 | 38 | def forward_request(self, method, path=None, 39 | json=None, params=None, headers=None): 40 | """Makes HTTP requests to the configured nodes. 41 | 42 | Retries connection errors 43 | (e.g. DNS failures, refused connection, etc). 44 | A user may choose to retry other errors 45 | by catching the corresponding 46 | exceptions and retrying `forward_request`. 47 | 48 | Exponential backoff is implemented individually for each node. 49 | Backoff delays are expressed as timestamps stored on the object and 50 | they are not reset in between multiple function calls. 51 | 52 | Times out when `self.timeout` is expired, if not `None`. 53 | 54 | Args: 55 | method (str): HTTP method name (e.g.: ``'GET'``). 56 | path (str): Path to be appended to the base url of a node. E.g.: 57 | ``'/transactions'``). 58 | json (dict): Payload to be sent with the HTTP request. 59 | params (dict)): Dictionary of URL (query) parameters. 60 | headers (dict): Optional headers to pass to the request. 61 | 62 | Returns: 63 | dict: Result of :meth:`requests.models.Response.json` 64 | 65 | """ 66 | error_trace = [] 67 | timeout = self.timeout 68 | backoff_cap = NO_TIMEOUT_BACKOFF_CAP if timeout is None \ 69 | else timeout / 2 70 | while timeout is None or timeout > 0: 71 | connection = self.connection_pool.get_connection() 72 | 73 | start = time() 74 | try: 75 | response = connection.request( 76 | method=method, 77 | path=path, 78 | params=params, 79 | json=json, 80 | headers=headers, 81 | timeout=timeout, 82 | backoff_cap=backoff_cap, 83 | ) 84 | except ConnectionError as err: 85 | error_trace.append(err) 86 | continue 87 | else: 88 | return response.data 89 | finally: 90 | elapsed = time() - start 91 | if timeout is not None: 92 | timeout -= elapsed 93 | 94 | raise TimeoutError(error_trace) 95 | -------------------------------------------------------------------------------- /bigchaindb_driver/utils.py: -------------------------------------------------------------------------------- 1 | # Copyright BigchainDB GmbH and BigchainDB contributors 2 | # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) 3 | # Code is Apache-2.0 and docs are CC-BY-4.0 4 | 5 | """Set of utilities to support various functionalities of the driver. 6 | 7 | Attributes: 8 | ops_map (dict): Mapping between operation strings and classes. 9 | E.g.: The string ``'CREATE'`` is mapped to 10 | :class:`~.CreateOperation`. 11 | """ 12 | from urllib.parse import urlparse, urlunparse 13 | 14 | DEFAULT_NODE = 'http://localhost:9984' 15 | 16 | 17 | class CreateOperation: 18 | """Class representing the ``'CREATE'`` transaction operation.""" 19 | 20 | 21 | class TransferOperation: 22 | """Class representing the ``'TRANSFER'`` transaction operation.""" 23 | 24 | 25 | ops_map = { 26 | 'CREATE': CreateOperation, 27 | 'TRANSFER': TransferOperation, 28 | } 29 | 30 | 31 | def _normalize_operation(operation): 32 | """Normalizes the given operation string. For now, this simply means 33 | converting the given string to uppercase, looking it up in 34 | :attr:`~.ops_map`, and returning the corresponding class if 35 | present. 36 | 37 | Args: 38 | operation (str): The operation string to convert. 39 | 40 | Returns: 41 | The class corresponding to the given string, 42 | :class:`~.CreateOperation` or :class:`~TransferOperation`. 43 | 44 | .. important:: If the :meth:`str.upper` step, or the 45 | :attr:`~.ops_map` lookup fails, the given ``operation`` 46 | argument is returned. 47 | 48 | """ 49 | try: 50 | operation = operation.upper() 51 | except AttributeError: 52 | pass 53 | 54 | try: 55 | operation = ops_map[operation]() 56 | except KeyError: 57 | pass 58 | 59 | return operation 60 | 61 | 62 | def _get_default_port(scheme): 63 | return 443 if scheme == 'https' else 9984 64 | 65 | 66 | def normalize_url(node): 67 | """Normalizes the given node url""" 68 | if not node: 69 | node = DEFAULT_NODE 70 | elif '://' not in node: 71 | node = '//{}'.format(node) 72 | parts = urlparse(node, scheme='http', allow_fragments=False) 73 | port = parts.port if parts.port else _get_default_port(parts.scheme) 74 | netloc = '{}:{}'.format(parts.hostname, port) 75 | return urlunparse((parts.scheme, netloc, parts.path, '', '', '')) 76 | 77 | 78 | def normalize_node(node, headers=None): 79 | """Normalizes given node as str or dict with headers""" 80 | headers = {} if headers is None else headers 81 | if isinstance(node, str): 82 | url = normalize_url(node) 83 | return {'endpoint': url, 'headers': headers} 84 | 85 | url = normalize_url(node['endpoint']) 86 | node_headers = node.get('headers', {}) 87 | return {'endpoint': url, 'headers': {**headers, **node_headers}} 88 | 89 | 90 | def normalize_nodes(*nodes, headers=None): 91 | """Normalizes given dict or array of driver nodes""" 92 | if not nodes: 93 | return (normalize_node(DEFAULT_NODE, headers),) 94 | 95 | normalized_nodes = () 96 | for node in nodes: 97 | normalized_nodes += (normalize_node(node, headers),) 98 | return normalized_nodes 99 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | # Copyright BigchainDB GmbH and BigchainDB contributors 2 | # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) 3 | # Code is Apache-2.0 and docs are CC-BY-4.0 4 | 5 | codecov: 6 | branch: master 7 | 8 | coverage: 9 | range: "95...100" 10 | 11 | ignore: 12 | - "docs/*" 13 | - "tests/*" 14 | - "bigchaindb_driver/common/*" 15 | 16 | comment: 17 | layout: "header, diff, changes, sunburst, uncovered" 18 | behavior: default 19 | -------------------------------------------------------------------------------- /compose/bigchaindb_driver/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.6 2 | 3 | RUN apt-get update && apt-get install -y vim 4 | 5 | RUN mkdir -p /usr/src/app 6 | WORKDIR /usr/src/app 7 | 8 | RUN pip install --upgrade pip 9 | 10 | COPY . /usr/src/app/ 11 | 12 | RUN pip install --no-cache-dir -e .[dev] -------------------------------------------------------------------------------- /compose/bigchaindb_server/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.6 2 | 3 | RUN apt-get update && apt-get install -y vim 4 | 5 | ARG branch 6 | ARG backend 7 | RUN mkdir -p /usr/src/app 8 | WORKDIR /usr/src/app 9 | 10 | RUN pip install --upgrade pip ipdb ipython 11 | 12 | COPY . /usr/src/app/ 13 | ENV BIGCHAINDB_DATABASE_BACKEND "${backend}" 14 | RUN pip install git+https://github.com/bigchaindb/bigchaindb.git@"${branch}"#egg=bigchaindb 15 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | # Copyright BigchainDB GmbH and BigchainDB contributors 2 | # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) 3 | # Code is Apache-2.0 and docs are CC-BY-4.0 4 | 5 | version: '2.1' 6 | 7 | services: 8 | bigchaindb-driver: 9 | depends_on: 10 | - bigchaindb 11 | build: 12 | context: . 13 | dockerfile: ./compose/bigchaindb_driver/Dockerfile 14 | volumes: 15 | - ./docs:/usr/src/app/docs 16 | - ./tests:/usr/src/app/tests 17 | - ./bigchaindb_driver:/usr/src/app/bigchaindb_driver 18 | - ./setup.py:/usr/src/app/setup.py 19 | - ./tox.ini:/usr/src/app/tox.ini 20 | - ./htmlcov:/usr/src/app/htmlcov 21 | - ./coverage.xml:/usr/src/app/coverage.xml 22 | environment: 23 | BDB_HOST: bigchaindb 24 | BIGCHAINDB_DATABASE_BACKEND: mongodb 25 | command: pytest -v 26 | mongodb: 27 | image: mongo:3.6 28 | ports: 29 | - "27017" 30 | command: mongod 31 | bigchaindb: 32 | depends_on: 33 | - mongodb 34 | - tendermint 35 | build: 36 | context: . 37 | dockerfile: ./compose/bigchaindb_server/Dockerfile 38 | args: 39 | branch: master 40 | backend: localmongodb 41 | environment: 42 | BIGCHAINDB_DATABASE_HOST: mongodb 43 | BIGCHAINDB_DATABASE_PORT: 27017 44 | BIGCHAINDB_SERVER_BIND: 0.0.0.0:9984 45 | BIGCHAINDB_WSSERVER_HOST: 0.0.0.0 46 | BIGCHAINDB_TENDERMINT_HOST: tendermint 47 | BIGCHAINDB_TENDERMINT_PORT: 26657 48 | ports: 49 | - "9984" 50 | - "9985" 51 | - "26658" 52 | healthcheck: 53 | test: ["CMD", "bash", "-c", "curl http://bigchaindb:9984 && curl http://tendermint:26657/abci_query"] 54 | interval: 3s 55 | timeout: 5s 56 | retries: 3 57 | command: bigchaindb -l DEBUG start 58 | tendermint: 59 | image: tendermint/tendermint:0.22.8 60 | entrypoint: '' 61 | ports: 62 | - "26656:26656" 63 | - "26657:26657" 64 | command: sh -c "tendermint init && tendermint node --consensus.create_empty_blocks=false --proxy_app=tcp://bigchaindb:26658" 65 | bdb: 66 | image: busybox 67 | depends_on: 68 | bigchaindb: 69 | condition: service_healthy 70 | # Build docs only 71 | # docker-compose build bdocs 72 | # docker-compose up -d bdocs 73 | bdocs: 74 | depends_on: 75 | - vdocs 76 | build: 77 | context: . 78 | dockerfile: ./compose/bigchaindb_driver/Dockerfile 79 | volumes: 80 | - .:/usr/src/app/ 81 | command: make -C docs html 82 | vdocs: 83 | image: nginx 84 | ports: 85 | - '55555:80' 86 | volumes: 87 | - ./docs/_build/html:/usr/share/nginx/html 88 | 89 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | 49 | clean: 50 | rm -rf $(BUILDDIR)/* 51 | 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | dirhtml: 58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 61 | 62 | singlehtml: 63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 64 | @echo 65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 66 | 67 | pickle: 68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 69 | @echo 70 | @echo "Build finished; now you can process the pickle files." 71 | 72 | json: 73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 74 | @echo 75 | @echo "Build finished; now you can process the JSON files." 76 | 77 | htmlhelp: 78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 79 | @echo 80 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 81 | ".hhp project file in $(BUILDDIR)/htmlhelp." 82 | 83 | qthelp: 84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 85 | @echo 86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/bigchaindb_driver.qhcp" 89 | @echo "To view the help file:" 90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/bigchaindb_driver.qhc" 91 | 92 | devhelp: 93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 94 | @echo 95 | @echo "Build finished." 96 | @echo "To view the help file:" 97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/bigchaindb_driver" 98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/bigchaindb_driver" 99 | @echo "# devhelp" 100 | 101 | epub: 102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 103 | @echo 104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 105 | 106 | latex: 107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 108 | @echo 109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 111 | "(use \`make latexpdf' here to do that automatically)." 112 | 113 | latexpdf: 114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 115 | @echo "Running LaTeX files through pdflatex..." 116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 118 | 119 | latexpdfja: 120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 121 | @echo "Running LaTeX files through platex and dvipdfmx..." 122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 124 | 125 | text: 126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 127 | @echo 128 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 129 | 130 | man: 131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 132 | @echo 133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 134 | 135 | texinfo: 136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 137 | @echo 138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 139 | @echo "Run \`make' in that directory to run these through makeinfo" \ 140 | "(use \`make info' here to do that automatically)." 141 | 142 | info: 143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 144 | @echo "Running Texinfo files through makeinfo..." 145 | make -C $(BUILDDIR)/texinfo info 146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 147 | 148 | gettext: 149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 150 | @echo 151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 152 | 153 | changes: 154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 155 | @echo 156 | @echo "The overview file is in $(BUILDDIR)/changes." 157 | 158 | linkcheck: 159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 160 | @echo 161 | @echo "Link check complete; look for any errors in the above output " \ 162 | "or in $(BUILDDIR)/linkcheck/output.txt." 163 | 164 | doctest: 165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 166 | @echo "Testing of doctests in the sources finished, look at the " \ 167 | "results in $(BUILDDIR)/doctest/output.txt." 168 | 169 | xml: 170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 171 | @echo 172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 173 | 174 | pseudoxml: 175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 176 | @echo 177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 178 | -------------------------------------------------------------------------------- /docs/_static/cc_escrow_execute_abort.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bigchaindb/bigchaindb-driver/504b6499c61142d9b596e3a21face4172065dffc/docs/_static/cc_escrow_execute_abort.png -------------------------------------------------------------------------------- /docs/_static/tx_escrow_execute_abort.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bigchaindb/bigchaindb-driver/504b6499c61142d9b596e3a21face4172065dffc/docs/_static/tx_escrow_execute_abort.png -------------------------------------------------------------------------------- /docs/_static/tx_multi_condition_multi_fulfillment_v1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bigchaindb/bigchaindb-driver/504b6499c61142d9b596e3a21face4172065dffc/docs/_static/tx_multi_condition_multi_fulfillment_v1.png -------------------------------------------------------------------------------- /docs/_static/tx_single_condition_single_fulfillment_v1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bigchaindb/bigchaindb-driver/504b6499c61142d9b596e3a21face4172065dffc/docs/_static/tx_single_condition_single_fulfillment_v1.png -------------------------------------------------------------------------------- /docs/aboutthedocs.rst: -------------------------------------------------------------------------------- 1 | 2 | .. Copyright BigchainDB GmbH and BigchainDB contributors 3 | SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) 4 | Code is Apache-2.0 and docs are CC-BY-4.0 5 | 6 | About this Documentation 7 | ======================== 8 | 9 | This section contains instructions to build and view the documentation locally, 10 | using the ``docker-compose.yml`` file of the ``bigchaindb-driver`` 11 | repository: https://github.com/bigchaindb/bigchaindb-driver. 12 | 13 | If you do not have a clone of the repo, you need to get one. 14 | 15 | 16 | Building the documentation 17 | -------------------------- 18 | To build the docs, simply run 19 | 20 | .. code-block:: bash 21 | 22 | $ docker-compose up -d bdocs 23 | 24 | Or if you prefer, start a ``bash`` session, 25 | 26 | .. code-block:: bash 27 | 28 | $ docker-compose run --rm bdocs bash 29 | 30 | and build the docs: 31 | 32 | .. code-block:: bash 33 | 34 | root@a651959a1f2d:/usr/src/app# make -C docs html 35 | 36 | 37 | Viewing the documentation 38 | ------------------------- 39 | The docs will be hosted on port **55555**, and can be accessed over 40 | [localhost](http:/localhost:33333), [127.0.0.1](http:/127.0.0.1:33333) 41 | OR http:/HOST_IP:33333. 42 | 43 | .. note:: If you are using ``docker-machine`` you need to replace ``localhost`` 44 | with the ``ip`` of the machine (e.g.: ``docker-machine ip tm`` if your 45 | machine is named ``tm``). 46 | 47 | 48 | Making changes 49 | -------------- 50 | The necessary source code is mounted, which allows you to make modifications, 51 | and view the changes by simply re-building the docs, and refreshing the 52 | browser. 53 | -------------------------------------------------------------------------------- /docs/advanced-installation.rst: -------------------------------------------------------------------------------- 1 | 2 | .. Copyright BigchainDB GmbH and BigchainDB contributors 3 | SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) 4 | Code is Apache-2.0 and docs are CC-BY-4.0 5 | 6 | Advanced Installation Options 7 | ============================= 8 | 9 | Installing from the Source Code 10 | ------------------------------- 11 | 12 | The source code for the BigchainDB Python Driver can be downloaded from the `Github repo`_. 13 | You can either clone the public repository: 14 | 15 | .. code-block:: bash 16 | 17 | git clone git://github.com/bigchaindb/bigchaindb-driver 18 | 19 | Or download the `tarball`_: 20 | 21 | .. code-block:: bash 22 | 23 | curl -OL https://github.com/bigchaindb/bigchaindb-driver/tarball/master 24 | 25 | Once you have a copy of the source code, you can install it by going to the directory containing ``setup.py`` and doing: 26 | 27 | .. code-block:: bash 28 | 29 | python setup.py install 30 | 31 | .. _Github repo: https://github.com/bigchaindb/bigchaindb-driver 32 | .. _tarball: https://github.com/bigchaindb/bigchaindb-driver/tarball/master -------------------------------------------------------------------------------- /docs/authors.rst: -------------------------------------------------------------------------------- 1 | 2 | .. Copyright BigchainDB GmbH and BigchainDB contributors 3 | SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) 4 | Code is Apache-2.0 and docs are CC-BY-4.0 5 | 6 | .. include:: ../AUTHORS.rst 7 | -------------------------------------------------------------------------------- /docs/changelog.rst: -------------------------------------------------------------------------------- 1 | 2 | .. Copyright BigchainDB GmbH and BigchainDB contributors 3 | SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) 4 | Code is Apache-2.0 and docs are CC-BY-4.0 5 | 6 | .. include:: ../CHANGELOG.rst 7 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Copyright BigchainDB GmbH and BigchainDB contributors 2 | # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) 3 | # Code is Apache-2.0 and docs are CC-BY-4.0 4 | 5 | import sys 6 | import os 7 | import datetime 8 | 9 | import sphinx_rtd_theme 10 | 11 | # Get the project root dir, which is the parent dir of this 12 | cwd = os.getcwd() 13 | project_root = os.path.dirname(cwd) 14 | 15 | # Insert the project root dir as the first element in the PYTHONPATH. 16 | # This lets us ensure that the source package is imported, and that its 17 | # version is used. 18 | sys.path.insert(0, project_root) 19 | 20 | import bigchaindb_driver 21 | 22 | 23 | extensions = [ 24 | 'sphinx.ext.autodoc', 25 | 'sphinx.ext.intersphinx', 26 | 'sphinx.ext.todo', 27 | 'sphinx.ext.coverage', 28 | 'sphinx.ext.napoleon', 29 | 'sphinx.ext.viewcode', 30 | 'sphinxcontrib.httpdomain', 31 | 'IPython.sphinxext.ipython_console_highlighting', 32 | 'IPython.sphinxext.ipython_directive', 33 | ] 34 | 35 | autodoc_default_options = { 36 | 'show-inheritance': None, 37 | } 38 | templates_path = ['_templates'] 39 | source_suffix = '.rst' 40 | master_doc = 'index' 41 | project = 'BigchainDB Python Driver' 42 | now = datetime.datetime.now() 43 | copyright = str(now.year) + ', BigchainDB Contributors' 44 | version = bigchaindb_driver.__version__ 45 | release = bigchaindb_driver.__version__ 46 | exclude_patterns = ['_build'] 47 | pygments_style = 'sphinx' 48 | todo_include_todos = True 49 | suppress_warnings = ['image.nonlocal_uri'] 50 | 51 | html_theme = 'sphinx_rtd_theme' 52 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] 53 | html_static_path = ['_static'] 54 | htmlhelp_basename = 'bigchaindb_python_driverdoc' 55 | 56 | latex_elements = {} 57 | 58 | latex_documents = [ 59 | ('index', 'bigchaindb_python_driver.tex', 60 | 'BigchainDB Python Driver Documentation', 61 | 'BigchainDB', 'manual'), 62 | ] 63 | 64 | man_pages = [ 65 | ('index', 'bigchaindb_python_driver', 66 | 'BigchainDB Python Driver Documentation', 67 | ['BigchainDB'], 1) 68 | ] 69 | 70 | texinfo_documents = [ 71 | ('index', 'bigchaindb_python_driver', 72 | 'BigchainDB Python Driver Documentation', 73 | 'BigchainDB', 74 | 'bigchaindb_python_driver', 75 | '', 76 | 'Miscellaneous'), 77 | ] 78 | 79 | intersphinx_mapping = { 80 | 'python': ('https://docs.python.org/3', None), 81 | 'bigchaindb-server': ( 82 | 'https://docs.bigchaindb.com/projects/server/en/latest/', None), 83 | } 84 | -------------------------------------------------------------------------------- /docs/connect.rst: -------------------------------------------------------------------------------- 1 | 2 | .. Copyright BigchainDB GmbH and BigchainDB contributors 3 | SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) 4 | Code is Apache-2.0 and docs are CC-BY-4.0 5 | 6 | .. _connect: 7 | 8 | ================================= 9 | Determine the BigchainDB Root URL 10 | ================================= 11 | 12 | If you want to use the BigchainDB Python Driver 13 | to communicate with a BigchainDB node or cluster, 14 | then you will need its BigchainDB Root URL. 15 | This page is to help you determine it. 16 | 17 | 18 | Case 1: BigchainDB on localhost 19 | ------------------------------- 20 | 21 | If a BigchainDB node is running locally 22 | (and the ``BIGCHAINDB_SERVER_BIND`` setting wasn't changed 23 | from the default ``localhost:9984``), 24 | then the BigchainDB Root URL is: 25 | 26 | .. code-block:: python 27 | 28 | bdb_root_url = 'http://localhost:9984' 29 | 30 | 31 | Case 2: A Cluster Hosted by Someone Else 32 | ---------------------------------------- 33 | 34 | If you're connecting to a BigchainDB cluster hosted 35 | by someone else, then they'll tell you their 36 | BigchaindB Root URL. 37 | It can take many forms. 38 | It can use HTTP or HTTPS. 39 | It can use a hostname or an IP address. 40 | The port might not be 9984. 41 | Here are some examples: 42 | 43 | .. code-block:: python 44 | 45 | bdb_root_url = 'http://example.com:9984' 46 | bdb_root_url = 'http://api.example.com:9984' 47 | bdb_root_url = 'http://example.com:1234' 48 | bdb_root_url = 'http://example.com' # http is port 80 by default 49 | bdb_root_url = 'https://example.com' # https is port 443 by default 50 | bdb_root_url = 'http://12.34.56.123:9984' 51 | bdb_root_url = 'http://12.34.56.123:5000' 52 | 53 | Case 3: Docker Container on localhost 54 | ------------------------------------- 55 | 56 | If you are running the Docker-based dev setup that comes along with the 57 | `bigchaindb_driver`_ repository (see :ref:`devenv-docker` for more 58 | information), and wish to connect to it from the ``bigchaindb-driver`` linked 59 | (container) service, use: 60 | 61 | .. code-block:: python 62 | 63 | bdb_root_url = 'http://bdb-server:9984' 64 | 65 | Alternatively, you may connect to the containerized BigchainDB node from 66 | "outside", in which case you need to know the port binding: 67 | 68 | .. code-block:: bash 69 | 70 | $ docker-compose port bigchaindb 9984 71 | 0.0.0.0:32780 72 | 73 | or you can use the command specified in the Makefile:: 74 | 75 | $ make root-url 76 | 0.0.0.0:32780 77 | 78 | .. code-block:: python 79 | 80 | bdb_root_url = 'http://0.0.0.0:32780' 81 | 82 | 83 | Next, try some of the :doc:`basic usage examples `. 84 | 85 | 86 | .. _bigchaindb_driver: https://github.com/bigchaindb/bigchaindb-driver 87 | -------------------------------------------------------------------------------- /docs/contributing.rst: -------------------------------------------------------------------------------- 1 | 2 | .. Copyright BigchainDB GmbH and BigchainDB contributors 3 | SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) 4 | Code is Apache-2.0 and docs are CC-BY-4.0 5 | 6 | .. include:: ../CONTRIBUTING.rst 7 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | 2 | .. Copyright BigchainDB GmbH and BigchainDB contributors 3 | SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) 4 | Code is Apache-2.0 and docs are CC-BY-4.0 5 | 6 | .. bigchaindb_driver documentation master file, created by 7 | sphinx-quickstart on Tue Jul 9 22:26:36 2013. 8 | You can adapt this file completely to your liking, but it should at least 9 | contain the root `toctree` directive. 10 | 11 | BigchainDB Python Driver 12 | ======================== 13 | 14 | .. toctree:: 15 | :maxdepth: 1 16 | 17 | ← Back to All BigchainDB Docs 18 | quickstart 19 | connect 20 | usage 21 | advanced-usage 22 | handcraft 23 | advanced-installation 24 | upgrading 25 | libref 26 | aboutthedocs 27 | contributing 28 | authors 29 | changelog 30 | 31 | Indices and tables 32 | ================== 33 | 34 | * :ref:`genindex` 35 | * :ref:`modindex` 36 | * :ref:`search` 37 | -------------------------------------------------------------------------------- /docs/libref.rst: -------------------------------------------------------------------------------- 1 | 2 | .. Copyright BigchainDB GmbH and BigchainDB contributors 3 | SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) 4 | Code is Apache-2.0 and docs are CC-BY-4.0 5 | 6 | Library Reference 7 | ================= 8 | 9 | .. automodule:: bigchaindb_driver 10 | 11 | ``driver`` 12 | ---------- 13 | 14 | .. autoclass:: BigchainDB 15 | :members: 16 | 17 | .. automethod:: __init__ 18 | 19 | .. automodule:: bigchaindb_driver.driver 20 | 21 | .. autoclass:: TransactionsEndpoint 22 | :members: 23 | 24 | .. autoclass:: OutputsEndpoint 25 | :members: 26 | 27 | .. autoclass:: AssetsEndpoint 28 | :members: 29 | 30 | .. autoclass:: NamespacedDriver 31 | :members: 32 | 33 | .. automethod:: __init__ 34 | 35 | 36 | ``offchain`` 37 | ------------ 38 | .. automodule:: bigchaindb_driver.offchain 39 | .. autofunction:: prepare_transaction 40 | .. autofunction:: prepare_create_transaction 41 | .. autofunction:: prepare_transfer_transaction 42 | .. autofunction:: fulfill_transaction 43 | 44 | 45 | ``transport`` 46 | ------------- 47 | .. automodule:: bigchaindb_driver.transport 48 | 49 | .. autoclass:: Transport 50 | :members: 51 | 52 | .. automethod:: __init__ 53 | 54 | ``pool`` 55 | -------- 56 | .. automodule:: bigchaindb_driver.pool 57 | 58 | .. autoclass:: Pool 59 | :members: 60 | 61 | .. automethod:: __init__ 62 | 63 | .. autoclass:: RoundRobinPicker 64 | :members: 65 | 66 | .. automethod:: __init__ 67 | 68 | .. autoclass:: AbstractPicker 69 | :members: 70 | 71 | 72 | ``connection`` 73 | -------------- 74 | .. automodule:: bigchaindb_driver.connection 75 | 76 | .. autoclass:: Connection 77 | :members: 78 | 79 | .. automethod:: __init__ 80 | 81 | 82 | ``crypto`` 83 | ---------- 84 | .. automodule:: bigchaindb_driver.crypto 85 | :members: 86 | 87 | 88 | ``exceptions`` 89 | -------------- 90 | 91 | .. automodule:: bigchaindb_driver.exceptions 92 | 93 | .. autoexception:: BigchaindbException 94 | 95 | .. autoexception:: TransportError 96 | 97 | .. autoexception:: ConnectionError 98 | 99 | .. autoexception:: NotFoundError 100 | 101 | .. autoexception:: KeypairNotFoundException 102 | 103 | .. autoexception:: InvalidPrivateKey 104 | 105 | .. autoexception:: InvalidPublicKey 106 | 107 | .. autoexception:: MissingPrivateKeyError 108 | 109 | 110 | ``utils`` 111 | --------- 112 | 113 | .. automodule:: bigchaindb_driver.utils 114 | :members: 115 | :private-members: 116 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | goto end 41 | ) 42 | 43 | if "%1" == "clean" ( 44 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 45 | del /q /s %BUILDDIR%\* 46 | goto end 47 | ) 48 | 49 | 50 | %SPHINXBUILD% 2> nul 51 | if errorlevel 9009 ( 52 | echo. 53 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 54 | echo.installed, then set the SPHINXBUILD environment variable to point 55 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 56 | echo.may add the Sphinx directory to PATH. 57 | echo. 58 | echo.If you don't have Sphinx installed, grab it from 59 | echo.http://sphinx-doc.org/ 60 | exit /b 1 61 | ) 62 | 63 | if "%1" == "html" ( 64 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 68 | goto end 69 | ) 70 | 71 | if "%1" == "dirhtml" ( 72 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 76 | goto end 77 | ) 78 | 79 | if "%1" == "singlehtml" ( 80 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 84 | goto end 85 | ) 86 | 87 | if "%1" == "pickle" ( 88 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can process the pickle files. 92 | goto end 93 | ) 94 | 95 | if "%1" == "json" ( 96 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 97 | if errorlevel 1 exit /b 1 98 | echo. 99 | echo.Build finished; now you can process the JSON files. 100 | goto end 101 | ) 102 | 103 | if "%1" == "htmlhelp" ( 104 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 105 | if errorlevel 1 exit /b 1 106 | echo. 107 | echo.Build finished; now you can run HTML Help Workshop with the ^ 108 | .hhp project file in %BUILDDIR%/htmlhelp. 109 | goto end 110 | ) 111 | 112 | if "%1" == "qthelp" ( 113 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 114 | if errorlevel 1 exit /b 1 115 | echo. 116 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 117 | .qhcp project file in %BUILDDIR%/qthelp, like this: 118 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\bigchaindb_driver.qhcp 119 | echo.To view the help file: 120 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\bigchaindb_driver.ghc 121 | goto end 122 | ) 123 | 124 | if "%1" == "devhelp" ( 125 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished. 129 | goto end 130 | ) 131 | 132 | if "%1" == "epub" ( 133 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 137 | goto end 138 | ) 139 | 140 | if "%1" == "latex" ( 141 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 145 | goto end 146 | ) 147 | 148 | if "%1" == "latexpdf" ( 149 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 150 | cd %BUILDDIR%/latex 151 | make all-pdf 152 | cd %BUILDDIR%/.. 153 | echo. 154 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 155 | goto end 156 | ) 157 | 158 | if "%1" == "latexpdfja" ( 159 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 160 | cd %BUILDDIR%/latex 161 | make all-pdf-ja 162 | cd %BUILDDIR%/.. 163 | echo. 164 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 165 | goto end 166 | ) 167 | 168 | if "%1" == "text" ( 169 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 170 | if errorlevel 1 exit /b 1 171 | echo. 172 | echo.Build finished. The text files are in %BUILDDIR%/text. 173 | goto end 174 | ) 175 | 176 | if "%1" == "man" ( 177 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 178 | if errorlevel 1 exit /b 1 179 | echo. 180 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 181 | goto end 182 | ) 183 | 184 | if "%1" == "texinfo" ( 185 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 186 | if errorlevel 1 exit /b 1 187 | echo. 188 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 189 | goto end 190 | ) 191 | 192 | if "%1" == "gettext" ( 193 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 194 | if errorlevel 1 exit /b 1 195 | echo. 196 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 197 | goto end 198 | ) 199 | 200 | if "%1" == "changes" ( 201 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 202 | if errorlevel 1 exit /b 1 203 | echo. 204 | echo.The overview file is in %BUILDDIR%/changes. 205 | goto end 206 | ) 207 | 208 | if "%1" == "linkcheck" ( 209 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 210 | if errorlevel 1 exit /b 1 211 | echo. 212 | echo.Link check complete; look for any errors in the above output ^ 213 | or in %BUILDDIR%/linkcheck/output.txt. 214 | goto end 215 | ) 216 | 217 | if "%1" == "doctest" ( 218 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 219 | if errorlevel 1 exit /b 1 220 | echo. 221 | echo.Testing of doctests in the sources finished, look at the ^ 222 | results in %BUILDDIR%/doctest/output.txt. 223 | goto end 224 | ) 225 | 226 | if "%1" == "xml" ( 227 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 228 | if errorlevel 1 exit /b 1 229 | echo. 230 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 231 | goto end 232 | ) 233 | 234 | if "%1" == "pseudoxml" ( 235 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 236 | if errorlevel 1 exit /b 1 237 | echo. 238 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 239 | goto end 240 | ) 241 | 242 | :end 243 | -------------------------------------------------------------------------------- /docs/quickstart.rst: -------------------------------------------------------------------------------- 1 | 2 | .. Copyright BigchainDB GmbH and BigchainDB contributors 3 | SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) 4 | Code is Apache-2.0 and docs are CC-BY-4.0 5 | 6 | ========================= 7 | Quickstart / Installation 8 | ========================= 9 | 10 | .. warning:: 11 | 12 | The instructions below were tested on Ubuntu 16.04 LTS. 13 | They should also work on other Linux distributions and on macOS. 14 | For other operating systems, we recommend using an Ubuntu virtual machine (VM). 15 | 16 | The BigchainDB Python Driver depends on: 17 | 18 | 1. Python 3.5+ 19 | 2. A recent Python 3 version of ``pip`` 20 | 3. A recent Python 3 version of ``setuptools`` 21 | 4. cryptography and cryptoconditions 22 | 23 | If you're missing one of those, then see "How to Install the Dependencies" below before proceeding. 24 | 25 | Next, to install the latest *stable* BigchainDB Python Driver (``bigchaindb_driver``) use: 26 | 27 | .. code-block:: bash 28 | 29 | $ pip3 install bigchaindb_driver 30 | 31 | If you want to install an Alpha, Beta or RC version of the Python Driver, use something like: 32 | 33 | .. code-block:: bash 34 | 35 | $ pip3 install bigchaindb_driver==0.5.0a4 36 | 37 | The above command will install version 0.5.0a4 (Alpha 4). 38 | You can find a list of all versions in 39 | `the release history page on PyPI `_. 40 | 41 | 42 | Next: :doc:`determine the BigchainDB Root URL of the BigchainDB node or cluster you want to connect to `. 43 | 44 | 45 | How to Install the Dependencies 46 | ------------------------------- 47 | 48 | 49 | Dependency 1: Python 3.5+ 50 | ^^^^^^^^^^^^^^^^^^^^^^^^^ 51 | 52 | The BigchainDB Python Driver uses Python 3.5+. You can check your version of Python using: 53 | 54 | .. code-block:: bash 55 | 56 | $ python --version 57 | 58 | OR 59 | 60 | $ python3 --version 61 | 62 | An easy way to install a specific version of Python, and to switch between versions of Python, is to use `virtualenv `_. Another option is `conda `_. 63 | 64 | 65 | Dependency 2: pip 66 | ^^^^^^^^^^^^^^^^^ 67 | 68 | You also need to get a recent, Python 3 version of ``pip``, the Python package manager. 69 | 70 | If you're using virtualenv or conda, then each virtual environment should include an appropriate version of ``pip``. 71 | 72 | You can check your version of ``pip`` using: 73 | 74 | .. code-block:: bash 75 | 76 | $ pip --version 77 | 78 | OR 79 | 80 | $ pip3 --version 81 | 82 | ``pip`` was at version 9.0.0 as of November 2016. 83 | If you need to upgrade your version of ``pip``, 84 | then see `the pip documentation `_. 85 | 86 | 87 | Dependency 3: setuptools 88 | ^^^^^^^^^^^^^^^^^^^^^^^^ 89 | 90 | Once you have a recent Python 3 version of ``pip``, you should be able to upgrade ``setuptools`` using: 91 | 92 | .. code-block:: bash 93 | 94 | $ pip install --upgrade setuptools 95 | 96 | OR 97 | 98 | $ pip3 install --upgrade setuptools 99 | 100 | 101 | Dependency 4: cryptography and cryptoconditions 102 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 103 | 104 | BigchainDB(server and driver) also depends on `cryptography`_ and `cryptoconditions`_. 105 | 106 | * `cryptography`_ depends on `libssl`_, `libcrypto`_ which also depends on (`Python development library and header files`_). 107 | * `cryptoconditions`_ depends on `PyNaCl`_ (`Networking and Cryptography library`_) which depends on ``ffi.h``. 108 | 109 | On Ubuntu 14.04 and 16.04, this works: 110 | 111 | .. code-block:: bash 112 | 113 | $ sudo apt-get update 114 | 115 | $ sudo apt-get install python3-dev libssl-dev libffi-dev 116 | 117 | For other operating systems, please refer to `the cryptography installation guide `_. 118 | 119 | 120 | Installing the Driver 121 | --------------------- 122 | 123 | Now you can install the BigchainDB Python Driver (``bigchaindb_driver``) using: 124 | 125 | .. code-block:: bash 126 | 127 | $ pip install bigchaindb_driver 128 | 129 | OR 130 | 131 | $ pip3 install bigchaindb_driver 132 | 133 | Next: :doc:`determine the BigchainDB Root URL of the BigchainDB node or cluster you want to connect to `. 134 | 135 | 136 | Advanced Installation Options 137 | ----------------------------- 138 | 139 | See the :doc:`Advanced Installation Options ` page. 140 | 141 | 142 | .. _pynacl: https://github.com/pyca/pynacl/ 143 | .. _Networking and Cryptography library: https://nacl.cr.yp.to/ 144 | .. _cryptoconditions: https://github.com/bigchaindb/cryptoconditions 145 | .. _cryptography: https://cryptography.io/en/latest/ 146 | .. _libssl-dev: https://packages.debian.org/jessie/libssl-dev 147 | .. _openssl-devel: https://rpmfind.net/linux/rpm2html/search.php?query=openssl-devel 148 | .. _libssl: https://github.com/openssl/openssl 149 | .. _libcrypto: https://github.com/openssl/openssl 150 | .. _Python development library and header files: https://github.com/python/cpython 151 | 152 | 153 | Installation Guide for Developers 154 | ---------------------------------- 155 | 156 | Here's how to set up `bigchaindb-driver`_ for local 157 | development. 158 | 159 | 1. Fork the `bigchaindb-driver`_ repo on GitHub. 160 | 2. Clone your fork locally and enter into the project:: 161 | 162 | $ git clone git@github.com:your_name_here/bigchaindb-driver.git 163 | $ cd bigchaindb-driver/ 164 | 165 | 3. Create a branch for local development:: 166 | 167 | $ git checkout -b name-of-your-bugfix-or-feature 168 | 169 | Now you can make your changes locally. 170 | 171 | 4. When you're done making changes, check that your changes pass flake8 172 | and the tests. For the tests, you'll need to start the MongoDB, Tendermint 173 | and BigchainDB servers:: 174 | 175 | $ docker-compose up -d bigchaindb 176 | 177 | 5. flake8 check:: 178 | 179 | $ docker-compose run --rm bigchaindb-driver flake8 bigchaindb_driver tests 180 | 181 | 6. To run the tests:: 182 | 183 | $ docker-compose run --rm bigchaindb-driver pytest -v 184 | 185 | 7. Commit your changes and push your branch to GitHub:: 186 | 187 | $ git add . 188 | $ git commit -m "Your detailed description of your changes." 189 | $ git push origin name-of-your-bugfix-or-feature 190 | 191 | .. 192 | 193 | We use pre-commit_ which should be triggered with every commit. Some hooks will change files but others will give errors that needs to be fixed. Every time a hook is failing you need to add the changed files again. 194 | The hooks we use can be found in the yaml_ config file. 195 | 196 | 8. Submit a pull request through the GitHub website. 197 | 198 | .. _pre-commit: http://pre-commit.com/ 199 | .. _yaml: https://github.com/bigchaindb/bigchaindb-driver/blob/master/.pre-commit-config.yaml 200 | .. _bigchaindb-driver: https://github.com/bigchaindb/bigchaindb-driver 201 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | # we need this for readthedocs 2 | 3 | Sphinx~=1.0 4 | sphinx-autobuild 5 | sphinxcontrib-autorun 6 | sphinxcontrib-napoleon>=0.4.4 7 | sphinx_rtd_theme 8 | sphinxcontrib-httpdomain 9 | ipython 10 | matplotlib 11 | -------------------------------------------------------------------------------- /docs/upgrading.rst: -------------------------------------------------------------------------------- 1 | 2 | .. Copyright BigchainDB GmbH and BigchainDB contributors 3 | SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) 4 | Code is Apache-2.0 and docs are CC-BY-4.0 5 | 6 | ========= 7 | Upgrading 8 | ========= 9 | 10 | Upgrading Using pip 11 | ------------------- 12 | 13 | If you installed the BigchainDB Python Driver using ``pip install bigchaindb_driver``, then you can upgrade it to the latest version using: 14 | 15 | .. code-block:: bash 16 | 17 | pip install --upgrade bigchaindb_driver 18 | 19 | -------------------------------------------------------------------------------- /media/repo-banner@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bigchaindb/bigchaindb-driver/504b6499c61142d9b596e3a21face4172065dffc/media/repo-banner@2x.png -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | env = 3 | D:BIGCHAINDB_KEYPAIR_PUBLIC=GW1nrdZm4mbVC8ePeiGWz6DqHexqewqy5teURVHi3RG4 4 | -------------------------------------------------------------------------------- /requirements_dev.txt: -------------------------------------------------------------------------------- 1 | pip==21.1 2 | bumpversion==0.5.3 3 | wheel==0.29.0 4 | git+https://github.com/bigchaindb/bigchaindb.git 5 | 6 | -e .[dev] 7 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Copyright BigchainDB GmbH and BigchainDB contributors 3 | # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) 4 | # Code is Apache-2.0 and docs are CC-BY-4.0 5 | 6 | from setuptools import setup, find_packages 7 | 8 | with open('README.rst') as readme_file: 9 | readme = readme_file.read() 10 | 11 | with open('CHANGELOG.rst') as changelog_file: 12 | changelog = changelog_file.read() 13 | 14 | install_requires = [ 15 | 'requests>=2.20.0', 16 | 'cryptoconditions==0.8.0', 17 | 'pysha3~=1.0.2', 18 | 'python-rapidjson~=0.6.0', 19 | 'python-rapidjson-schema==0.1.1', 20 | ] 21 | 22 | tests_require = [ 23 | 'tox>=2.3.1', 24 | 'coverage>=4.1', 25 | 'flake8>=2.6.0', 26 | 'pytest>=3.0.1', 27 | 'pytest-cov', 28 | 'pytest-env', 29 | 'pytest-sugar', 30 | 'pytest-xdist', 31 | 'responses~=0.5.1', 32 | ] 33 | 34 | dev_require = [ 35 | 'ipdb', 36 | 'ipython', 37 | 'pre-commit' 38 | ] 39 | 40 | docs_require = [ 41 | 'Sphinx~=1.0', 42 | 'sphinx-autobuild', 43 | 'sphinxcontrib-autorun', 44 | 'sphinxcontrib-napoleon>=0.4.4', 45 | 'sphinx_rtd_theme', 46 | 'sphinxcontrib-httpdomain', 47 | 'matplotlib', 48 | ] 49 | 50 | setup( 51 | name='bigchaindb_driver', 52 | version='0.6.2', 53 | description="Python driver for BigchainDB", 54 | long_description=readme + '\n\n' + changelog, 55 | author="BigchainDB", 56 | author_email='devs@bigchaindb.com', 57 | url='https://github.com/bigchaindb/bigchaindb-driver', 58 | packages=find_packages(exclude=['tests*']), 59 | package_dir={'bigchaindb_driver': 60 | 'bigchaindb_driver'}, 61 | include_package_data=True, 62 | install_requires=install_requires, 63 | python_requires='>=3.5', 64 | license="Apache Software License 2.0", 65 | zip_safe=False, 66 | keywords='bigchaindb_driver', 67 | classifiers=[ 68 | 'Development Status :: 5 - Production/Stable', 69 | 'Intended Audience :: Developers', 70 | 'License :: OSI Approved :: Apache Software License', 71 | 'Natural Language :: English', 72 | 'Programming Language :: Python :: 3 :: Only', 73 | 'Programming Language :: Python :: 3.5', 74 | 'Programming Language :: Python :: 3.6', 75 | ], 76 | test_suite='tests', 77 | extras_require={ 78 | 'test': tests_require, 79 | 'dev': dev_require + tests_require + docs_require, 80 | 'docs': docs_require, 81 | }, 82 | ) 83 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bigchaindb/bigchaindb-driver/504b6499c61142d9b596e3a21face4172065dffc/tests/__init__.py -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | # Copyright BigchainDB GmbH and BigchainDB contributors 2 | # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) 3 | # Code is Apache-2.0 and docs are CC-BY-4.0 4 | 5 | import json 6 | from base64 import b64encode 7 | from collections import namedtuple 8 | from os import environ, urandom 9 | 10 | import requests 11 | import base58 12 | from cryptoconditions import Ed25519Sha256 13 | from pytest import fixture 14 | from sha3 import sha3_256 15 | 16 | from bigchaindb_driver.common.transaction import Transaction, \ 17 | _fulfillment_to_details 18 | 19 | 20 | def make_ed25519_condition(public_key, *, amount=1): 21 | ed25519 = Ed25519Sha256(public_key=base58.b58decode(public_key)) 22 | return { 23 | 'amount': str(amount), 24 | 'condition': { 25 | 'details': _fulfillment_to_details(ed25519), 26 | 'uri': ed25519.condition_uri, 27 | }, 28 | 'public_keys': (public_key,), 29 | } 30 | 31 | 32 | def make_fulfillment(*public_keys, input_=None): 33 | return { 34 | 'fulfillment': None, 35 | 'fulfills': input_, 36 | 'owners_before': public_keys, 37 | } 38 | 39 | 40 | def serialize_transaction(transaction): 41 | return json.dumps( 42 | transaction, 43 | sort_keys=True, 44 | separators=(',', ':'), 45 | ensure_ascii=False, 46 | ) 47 | 48 | 49 | def hash_transaction(transaction): 50 | return sha3_256(serialize_transaction(transaction).encode()).hexdigest() 51 | 52 | 53 | def set_transaction_id(transaction): 54 | tx_id = hash_transaction(transaction) 55 | transaction['id'] = tx_id 56 | 57 | 58 | def sign_transaction(transaction, *, public_key, private_key): 59 | ed25519 = Ed25519Sha256(public_key=base58.b58decode(public_key)) 60 | message = json.dumps( 61 | transaction, 62 | sort_keys=True, 63 | separators=(',', ':'), 64 | ensure_ascii=False, 65 | ) 66 | message = sha3_256(message.encode()) 67 | ed25519.sign(message.digest(), base58.b58decode(private_key)) 68 | return ed25519.serialize_uri() 69 | 70 | 71 | @fixture 72 | def alice_privkey(): 73 | return 'CT6nWhSyE7dF2znpx3vwXuceSrmeMy9ChBfi9U92HMSP' 74 | 75 | 76 | @fixture 77 | def alice_pubkey(): 78 | return 'G7J7bXF8cqSrjrxUKwcF8tCriEKC5CgyPHmtGwUi4BK3' 79 | 80 | 81 | @fixture 82 | def alice_keypair(alice_privkey, alice_pubkey): 83 | keypair = namedtuple('alice_keypair', ['pubkey', 'privkey']) 84 | keypair.vk = alice_pubkey 85 | keypair.sk = alice_privkey 86 | return keypair 87 | 88 | 89 | @fixture 90 | def bob_privkey(): 91 | return '4S1dzx3PSdMAfs59aBkQefPASizTs728HnhLNpYZWCad' 92 | 93 | 94 | @fixture 95 | def bob_pubkey(): 96 | return '2dBVUoATxEzEqRdsi64AFsJnn2ywLCwnbNwW7K9BuVuS' 97 | 98 | 99 | @fixture 100 | def bob_keypair(bob_privkey, bob_pubkey): 101 | keypair = namedtuple('bob_keypair', ['pubkey', 'privkey']) 102 | keypair.vk = bob_pubkey 103 | keypair.sk = bob_privkey 104 | return keypair 105 | 106 | 107 | @fixture 108 | def carol_keypair(): 109 | from bigchaindb_driver.crypto import generate_keypair 110 | return generate_keypair() 111 | 112 | 113 | @fixture 114 | def carol_privkey(carol_keypair): 115 | return carol_keypair.private_key 116 | 117 | 118 | @fixture 119 | def carol_pubkey(carol_keypair): 120 | return carol_keypair.public_key 121 | 122 | 123 | @fixture 124 | def dimi_keypair(): 125 | from bigchaindb_driver.crypto import generate_keypair 126 | return generate_keypair() 127 | 128 | 129 | @fixture 130 | def dimi_privkey(dimi_keypair): 131 | return dimi_keypair.private_key 132 | 133 | 134 | @fixture 135 | def dimi_pubkey(dimi_keypair): 136 | return dimi_keypair.public_key 137 | 138 | 139 | @fixture 140 | def ewy_keypair(): 141 | from bigchaindb_driver.crypto import generate_keypair 142 | return generate_keypair() 143 | 144 | 145 | @fixture 146 | def ewy_privkey(ewy_keypair): 147 | return ewy_keypair.private_key 148 | 149 | 150 | @fixture 151 | def ewy_pubkey(ewy_keypair): 152 | return ewy_keypair.public_key 153 | 154 | 155 | @fixture 156 | def bdb_host(): 157 | return environ.get('BDB_HOST', 'localhost') 158 | 159 | 160 | @fixture 161 | def bdb_port(): 162 | return environ.get('BDB_PORT', '9984') 163 | 164 | 165 | @fixture 166 | def custom_headers(): 167 | return {'app_id': 'id'} 168 | 169 | 170 | @fixture 171 | def bdb_node(bdb_host, bdb_port): 172 | return 'http://{host}:{port}'.format(host=bdb_host, port=bdb_port) 173 | 174 | 175 | @fixture 176 | def bdb_nodes(bdb_node, custom_headers): 177 | return [ 178 | {'endpoint': 'http://unavailable'}, # unavailable node 179 | {'endpoint': bdb_node, 'headers': custom_headers}, 180 | ] 181 | 182 | 183 | @fixture 184 | def driver_multiple_nodes(bdb_nodes): 185 | from bigchaindb_driver import BigchainDB 186 | return BigchainDB(*bdb_nodes) 187 | 188 | 189 | @fixture 190 | def driver(bdb_node): 191 | from bigchaindb_driver import BigchainDB 192 | return BigchainDB(bdb_node) 193 | 194 | 195 | @fixture 196 | def api_root(bdb_node): 197 | return bdb_node + '/api/v1' 198 | 199 | 200 | @fixture 201 | def transactions_api_full_url(api_root): 202 | return api_root + '/transactions?mode=commit' 203 | 204 | 205 | @fixture 206 | def blocks_api_full_url(api_root): 207 | return api_root + '/blocks' 208 | 209 | 210 | @fixture 211 | def mock_requests_post(monkeypatch): 212 | class MockResponse: 213 | def __init__(self, json): 214 | self._json = json 215 | 216 | def json(self): 217 | return self._json 218 | 219 | def mockreturn(*args, **kwargs): 220 | return MockResponse(kwargs.get('json')) 221 | 222 | monkeypatch.setattr('requests.post', mockreturn) 223 | 224 | 225 | @fixture 226 | def alice_transaction_obj(alice_pubkey): 227 | serial_number = b64encode(urandom(10), altchars=b'-_').decode() 228 | return Transaction.create( 229 | tx_signers=[alice_pubkey], 230 | recipients=[([alice_pubkey], 1)], 231 | asset={'serial_number': serial_number}, 232 | ) 233 | 234 | 235 | @fixture 236 | def alice_transaction(alice_transaction_obj): 237 | return alice_transaction_obj.to_dict() 238 | 239 | 240 | @fixture 241 | def signed_alice_transaction(alice_privkey, alice_transaction_obj): 242 | signed_transaction = alice_transaction_obj.sign([alice_privkey]) 243 | return signed_transaction.to_dict() 244 | 245 | 246 | @fixture 247 | def persisted_alice_transaction(signed_alice_transaction, 248 | transactions_api_full_url): 249 | response = requests.post(transactions_api_full_url, 250 | json=signed_alice_transaction) 251 | return response.json() 252 | 253 | 254 | @fixture 255 | def persisted_random_transaction(alice_pubkey, 256 | alice_privkey): 257 | from uuid import uuid4 258 | from bigchaindb_driver.common.transaction import Transaction 259 | asset = {'data': {'x': str(uuid4())}} 260 | tx = Transaction.create( 261 | tx_signers=[alice_pubkey], 262 | recipients=[([alice_pubkey], 1)], 263 | asset=asset, 264 | ) 265 | return tx.sign([alice_privkey]).to_dict() 266 | 267 | 268 | @fixture 269 | def sent_persisted_random_transaction(alice_pubkey, 270 | alice_privkey, 271 | transactions_api_full_url): 272 | from uuid import uuid4 273 | from bigchaindb_driver.common.transaction import Transaction 274 | asset = {'data': {'x': str(uuid4())}} 275 | tx = Transaction.create( 276 | tx_signers=[alice_pubkey], 277 | recipients=[([alice_pubkey], 1)], 278 | asset=asset, 279 | ) 280 | tx_signed = tx.sign([alice_privkey]) 281 | response = requests.post(transactions_api_full_url, 282 | json=tx_signed.to_dict()) 283 | return response.json() 284 | 285 | 286 | @fixture 287 | def block_with_alice_transaction(sent_persisted_random_transaction, 288 | blocks_api_full_url): 289 | return requests.get( 290 | blocks_api_full_url, 291 | params={'transaction_id': sent_persisted_random_transaction['id']} 292 | ).json()[0] 293 | 294 | 295 | @fixture 296 | def bicycle_data(): 297 | return { 298 | 'bicycle': { 299 | 'manufacturer': 'bkfab', 300 | 'serial_number': 'abcd1234', 301 | }, 302 | } 303 | 304 | 305 | @fixture 306 | def car_data(): 307 | return { 308 | 'car': { 309 | 'manufacturer': 'bkfab', 310 | 'vin': '5YJRE11B781000196', 311 | }, 312 | } 313 | 314 | 315 | @fixture 316 | def prepared_carol_bicycle_transaction(carol_keypair, bicycle_data): 317 | condition = make_ed25519_condition(carol_keypair.public_key) 318 | fulfillment = make_fulfillment(carol_keypair.public_key) 319 | tx = { 320 | 'asset': { 321 | 'data': bicycle_data, 322 | }, 323 | 'metadata': None, 324 | 'operation': 'CREATE', 325 | 'outputs': (condition,), 326 | 'inputs': (fulfillment,), 327 | 'version': '2.0', 328 | 'id': None, 329 | } 330 | return tx 331 | 332 | 333 | @fixture 334 | def signed_carol_bicycle_transaction(request, carol_keypair, 335 | prepared_carol_bicycle_transaction): 336 | fulfillment_uri = sign_transaction( 337 | prepared_carol_bicycle_transaction, 338 | public_key=carol_keypair.public_key, 339 | private_key=carol_keypair.private_key, 340 | ) 341 | prepared_carol_bicycle_transaction['inputs'][0].update( 342 | {'fulfillment': fulfillment_uri}, 343 | ) 344 | set_transaction_id(prepared_carol_bicycle_transaction) 345 | return prepared_carol_bicycle_transaction 346 | 347 | 348 | @fixture 349 | def persisted_carol_bicycle_transaction(transactions_api_full_url, 350 | signed_carol_bicycle_transaction): 351 | response = requests.post( 352 | transactions_api_full_url, json=signed_carol_bicycle_transaction) 353 | return response.json() 354 | 355 | 356 | @fixture 357 | def prepared_carol_car_transaction(carol_keypair, car_data): 358 | condition = make_ed25519_condition(carol_keypair.public_key) 359 | fulfillment = make_fulfillment(carol_keypair.public_key) 360 | tx = { 361 | 'asset': { 362 | 'data': car_data, 363 | }, 364 | 'metadata': None, 365 | 'operation': 'CREATE', 366 | 'outputs': (condition,), 367 | 'inputs': (fulfillment,), 368 | 'version': '2.0', 369 | 'id': None, 370 | } 371 | return tx 372 | 373 | 374 | @fixture 375 | def signed_carol_car_transaction(request, carol_keypair, 376 | prepared_carol_car_transaction): 377 | fulfillment_uri = sign_transaction( 378 | prepared_carol_car_transaction, 379 | public_key=carol_keypair.public_key, 380 | private_key=carol_keypair.private_key, 381 | ) 382 | prepared_carol_car_transaction['inputs'][0].update( 383 | {'fulfillment': fulfillment_uri}, 384 | ) 385 | set_transaction_id(prepared_carol_car_transaction) 386 | return prepared_carol_car_transaction 387 | 388 | 389 | @fixture 390 | def persisted_carol_car_transaction(transactions_api_full_url, 391 | signed_carol_car_transaction): 392 | response = requests.post( 393 | transactions_api_full_url, json=signed_carol_car_transaction) 394 | return response.json() 395 | 396 | 397 | @fixture 398 | def persisted_transfer_carol_car_to_dimi(carol_keypair, dimi_pubkey, 399 | transactions_api_full_url, 400 | persisted_carol_car_transaction): 401 | output_txid = persisted_carol_car_transaction['id'] 402 | ed25519_dimi = Ed25519Sha256(public_key=base58.b58decode(dimi_pubkey)) 403 | transaction = { 404 | 'asset': {'id': output_txid}, 405 | 'metadata': None, 406 | 'operation': 'TRANSFER', 407 | 'outputs': ({ 408 | 'amount': '1', 409 | 'condition': { 410 | 'details': _fulfillment_to_details(ed25519_dimi), 411 | 'uri': ed25519_dimi.condition_uri, 412 | }, 413 | 'public_keys': (dimi_pubkey,), 414 | },), 415 | 'inputs': ({ 416 | 'fulfillment': None, 417 | 'fulfills': { 418 | 'output_index': 0, 419 | 'transaction_id': output_txid, 420 | }, 421 | 'owners_before': (carol_keypair.public_key,), 422 | },), 423 | 'version': '2.0', 424 | 'id': None, 425 | } 426 | serialized_transaction = json.dumps( 427 | transaction, 428 | sort_keys=True, 429 | separators=(',', ':'), 430 | ensure_ascii=False, 431 | ) 432 | serialized_transaction = sha3_256(serialized_transaction.encode()) 433 | 434 | if transaction['inputs'][0]['fulfills']: 435 | serialized_transaction.update('{}{}'.format( 436 | transaction['inputs'][0]['fulfills']['transaction_id'], 437 | transaction['inputs'][0]['fulfills']['output_index']).encode()) 438 | 439 | ed25519_carol = Ed25519Sha256( 440 | public_key=base58.b58decode(carol_keypair.public_key)) 441 | ed25519_carol.sign(serialized_transaction.digest(), 442 | base58.b58decode(carol_keypair.private_key)) 443 | transaction['inputs'][0]['fulfillment'] = ed25519_carol.serialize_uri() 444 | set_transaction_id(transaction) 445 | response = requests.post(transactions_api_full_url, json=transaction) 446 | return response.json() 447 | 448 | 449 | @fixture 450 | def persisted_transfer_dimi_car_to_ewy(dimi_keypair, ewy_pubkey, 451 | transactions_api_full_url, 452 | persisted_transfer_carol_car_to_dimi): 453 | output_txid = persisted_transfer_carol_car_to_dimi['id'] 454 | ed25519_ewy = Ed25519Sha256(public_key=base58.b58decode(ewy_pubkey)) 455 | transaction = { 456 | 'asset': {'id': persisted_transfer_carol_car_to_dimi['asset']['id']}, 457 | 'metadata': None, 458 | 'operation': 'TRANSFER', 459 | 'outputs': ({ 460 | 'amount': '1', 461 | 'condition': { 462 | 'details': _fulfillment_to_details(ed25519_ewy), 463 | 'uri': ed25519_ewy.condition_uri, 464 | }, 465 | 'public_keys': (ewy_pubkey,), 466 | },), 467 | 'inputs': ({ 468 | 'fulfillment': None, 469 | 'fulfills': { 470 | 'output_index': 0, 471 | 'transaction_id': output_txid, 472 | }, 473 | 'owners_before': (dimi_keypair.public_key,), 474 | },), 475 | 'version': '2.0', 476 | 'id': None, 477 | } 478 | serialized_transaction = json.dumps( 479 | transaction, 480 | sort_keys=True, 481 | separators=(',', ':'), 482 | ensure_ascii=False, 483 | ) 484 | serialized_transaction = sha3_256(serialized_transaction.encode()) 485 | if transaction['inputs'][0]['fulfills']: 486 | serialized_transaction.update('{}{}'.format( 487 | transaction['inputs'][0]['fulfills']['transaction_id'], 488 | transaction['inputs'][0]['fulfills']['output_index']).encode()) 489 | 490 | ed25519_dimi = Ed25519Sha256( 491 | public_key=base58.b58decode(dimi_keypair.public_key)) 492 | ed25519_dimi.sign(serialized_transaction.digest(), 493 | base58.b58decode(dimi_keypair.private_key)) 494 | transaction['inputs'][0]['fulfillment'] = ed25519_dimi.serialize_uri() 495 | set_transaction_id(transaction) 496 | response = requests.post(transactions_api_full_url, json=transaction) 497 | return response.json() 498 | 499 | 500 | @fixture 501 | def unsigned_transaction(): 502 | return { 503 | 'operation': 'CREATE', 504 | 'asset': { 505 | 'data': { 506 | 'serial_number': 'NNP43x-DaYoSWg==' 507 | } 508 | }, 509 | 'version': '2.0', 510 | 'outputs': [ 511 | { 512 | 'condition': { 513 | 'details': { 514 | 'public_key': 'G7J7bXF8cqSrjrxUKwcF8tCriEKC5CgyPHmtGwUi4BK3', # noqa E501 515 | 'type': 'ed25519-sha-256' 516 | }, 517 | 'uri': 'ni:///sha-256;7U_VA9u_5e4hsgGkaxO_n0W3ZtSlzhCNYWV6iEYU7mo?fpt=ed25519-sha-256&cost=131072' # noqa E501 518 | }, 519 | 'public_keys': [ 520 | 'G7J7bXF8cqSrjrxUKwcF8tCriEKC5CgyPHmtGwUi4BK3' 521 | ], 522 | 'amount': '1' 523 | } 524 | ], 525 | 'inputs': [ 526 | { 527 | 'fulfills': None, 528 | 'owners_before': [ 529 | 'G7J7bXF8cqSrjrxUKwcF8tCriEKC5CgyPHmtGwUi4BK3' 530 | ], 531 | 'fulfillment': { 532 | 'public_key': 'G7J7bXF8cqSrjrxUKwcF8tCriEKC5CgyPHmtGwUi4BK3', # noqa E501 533 | 'type': 'ed25519-sha-256' 534 | } 535 | } 536 | ], 537 | 'id': None, 538 | 'metadata': None 539 | } 540 | 541 | 542 | @fixture 543 | def text_search_assets(api_root, transactions_api_full_url, alice_pubkey, 544 | alice_privkey): 545 | # check if the fixture was already executed 546 | response = requests.get(api_root + '/assets', 547 | params={'search': 'bigchaindb'}) 548 | response = response.json() 549 | if len(response) == 3: 550 | assets = {} 551 | for asset in response: 552 | assets[asset['id']] = asset['data'] 553 | return assets 554 | 555 | # define the assets that will be used by text_search tests 556 | assets = [ 557 | {'msg': 'Hello BigchainDB 1!'}, 558 | {'msg': 'Hello BigchainDB 2!'}, 559 | {'msg': 'Hello BigchainDB 3!'} 560 | ] 561 | 562 | # write the assets to BigchainDB 563 | assets_by_txid = {} 564 | for asset in assets: 565 | tx = Transaction.create( 566 | tx_signers=[alice_pubkey], 567 | recipients=[([alice_pubkey], 1)], 568 | asset=asset, 569 | metadata={'But here\'s my number': 'So call me maybe'}, 570 | ) 571 | tx_signed = tx.sign([alice_privkey]) 572 | requests.post(transactions_api_full_url, json=tx_signed.to_dict()) 573 | assets_by_txid[tx_signed.id] = asset 574 | 575 | # return the assets indexed with the txid that created the asset 576 | return assets_by_txid 577 | -------------------------------------------------------------------------------- /tests/test_connection.py: -------------------------------------------------------------------------------- 1 | # Copyright BigchainDB GmbH and BigchainDB contributors 2 | # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) 3 | # Code is Apache-2.0 and docs are CC-BY-4.0 4 | 5 | from pytest import mark 6 | from requests.utils import default_headers 7 | from responses import RequestsMock 8 | 9 | 10 | class TestConnection: 11 | 12 | def test_init_with_custom_headers(self): 13 | from bigchaindb_driver.connection import Connection 14 | url = 'http://dummy' 15 | custom_headers = {'app_id': 'id_value', 'app_key': 'key_value'} 16 | connection = Connection(node_url=url, headers=custom_headers) 17 | expected_headers = default_headers() 18 | expected_headers.update(custom_headers) 19 | assert connection.session.headers == expected_headers 20 | 21 | @mark.parametrize('content_type,json,data', ( 22 | ('application/json', {'a': 1}, {'a': 1}), 23 | ('text/plain', {}, ''), 24 | )) 25 | def test_response_content_type_handling(self, content_type, json, data): 26 | from bigchaindb_driver.connection import Connection 27 | url = 'http://dummy' 28 | connection = Connection(node_url=url) 29 | with RequestsMock() as requests_mock: 30 | requests_mock.add('GET', url, json=json) 31 | response = connection.request('GET') 32 | assert response.status_code == 200 33 | assert response.headers['Content-Type'] == content_type 34 | assert response.data == data 35 | 36 | @mark.parametrize('headers', ( 37 | {}, {'app_name': 'name'}, {'app_id': 'id', 'app_key': 'key'} 38 | )) 39 | def test_request_with_headers(self, headers): 40 | from bigchaindb_driver.connection import Connection 41 | url = 'http://dummy' 42 | connection = Connection(node_url=url, headers=headers) 43 | with RequestsMock() as requests_mock: 44 | requests_mock.add('GET', url, adding_headers=headers) 45 | response = connection.request('GET') 46 | assert response.status_code == 200 47 | del response.headers['Content-type'] 48 | assert response.headers == headers 49 | -------------------------------------------------------------------------------- /tests/test_crypto.py: -------------------------------------------------------------------------------- 1 | # Copyright BigchainDB GmbH and BigchainDB contributors 2 | # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) 3 | # Code is Apache-2.0 and docs are CC-BY-4.0 4 | 5 | 6 | def test_generate_keypair(): 7 | from bigchaindb_driver.crypto import CryptoKeypair, generate_keypair 8 | keypair = generate_keypair() 9 | assert len(keypair) == 2 10 | assert isinstance(keypair, CryptoKeypair) 11 | assert isinstance(keypair.private_key, str) 12 | assert isinstance(keypair.public_key, str) 13 | -------------------------------------------------------------------------------- /tests/test_driver.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Copyright BigchainDB GmbH and BigchainDB contributors 3 | # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) 4 | # Code is Apache-2.0 and docs are CC-BY-4.0 5 | 6 | 7 | import json 8 | 9 | import base58 10 | from pytest import mark, raises 11 | from requests.utils import default_headers 12 | from sha3 import sha3_256 13 | from cryptoconditions import Ed25519Sha256 14 | 15 | 16 | class TestBigchainDB: 17 | 18 | @mark.parametrize('nodes,headers, normalized_nodes', ( 19 | ((), None, (({'endpoint': 'http://localhost:9984', 'headers': {}},))), 20 | (('node-1',), None, 21 | ({'endpoint': 'http://node-1:9984', 'headers': {}},)), 22 | (('node-1', 23 | 'node-2', 24 | ), 25 | {'app_id': 'id'}, 26 | ({'endpoint': 'http://node-1:9984', 27 | 'headers': {'app_id': 'id'}}, 28 | {'endpoint': 'http://node-2:9984', 29 | 'headers': {'app_id': 'id'}}, 30 | )), 31 | (({'endpoint': 'node-1', 32 | 'headers': {'app_id': 'id'}}, 33 | {'endpoint': 'node-2', 34 | 'headers': {'app_id': 'id'}},), 35 | None, 36 | ({'endpoint': 'http://node-1:9984', 37 | 'headers': {'app_id': 'id'}}, 38 | {'endpoint': 'http://node-2:9984', 39 | 'headers': {'app_id': 'id'}},)), 40 | 41 | )) 42 | def test_driver_init(self, nodes, headers, normalized_nodes): 43 | from bigchaindb_driver.driver import BigchainDB 44 | driver = BigchainDB(*nodes, headers=headers) 45 | nodes = normalized_nodes 46 | headers = {} if not headers else headers 47 | assert driver.nodes == normalized_nodes 48 | assert driver.transport.nodes == normalized_nodes 49 | expected_headers = default_headers() 50 | expected_headers.update(headers) 51 | for conn in driver.transport.connection_pool.connections: 52 | conn.session.headers == expected_headers 53 | assert driver.transactions 54 | assert driver.outputs 55 | 56 | def test_info(self, driver): 57 | response = driver.info() 58 | assert 'api' in response 59 | assert 'docs' in response 60 | assert response['software'] == 'BigchainDB' 61 | assert 'version' in response 62 | 63 | def test_api_info(self, driver): 64 | response = driver.api_info() 65 | assert 'docs' in response 66 | assert response['assets'] == '/assets/' 67 | assert response['outputs'] == '/outputs/' 68 | assert response['transactions'] == '/transactions/' 69 | 70 | 71 | class TestTransactionsEndpoint: 72 | 73 | def test_retrieve(self, driver, sent_persisted_random_transaction): 74 | txid = sent_persisted_random_transaction['id'] 75 | tx = driver.transactions.retrieve(txid) 76 | assert tx['id'] == txid 77 | 78 | def test_retrieve_not_found(self, driver): 79 | from bigchaindb_driver.exceptions import NotFoundError 80 | txid = 'dummy_id' 81 | with raises(NotFoundError): 82 | driver.transactions.retrieve(txid) 83 | 84 | def test_prepare(self, driver, alice_pubkey): 85 | transaction = driver.transactions.prepare(signers=[alice_pubkey]) 86 | assert 'id' in transaction 87 | assert 'version' in transaction 88 | assert 'asset' in transaction 89 | assert 'outputs' in transaction 90 | assert 'inputs' in transaction 91 | assert 'metadata' in transaction 92 | assert 'operation' in transaction 93 | assert transaction['operation'] == 'CREATE' 94 | outputs = transaction['outputs'] 95 | assert len(outputs) == 1 96 | assert len(outputs[0]['public_keys']) == 1 97 | assert outputs[0]['public_keys'][0] == alice_pubkey 98 | inputs = transaction['inputs'] 99 | assert inputs[0]['owners_before'][0] == alice_pubkey 100 | assert len(inputs) == 1 101 | assert len(inputs[0]['owners_before']) == 1 102 | assert inputs[0]['owners_before'][0] == alice_pubkey 103 | assert not transaction['metadata'] 104 | 105 | def test_fulfill(self, driver, alice_keypair, unsigned_transaction): 106 | signed_transaction = driver.transactions.fulfill( 107 | unsigned_transaction, private_keys=alice_keypair.sk) 108 | unsigned_transaction['inputs'][0]['fulfillment'] = None 109 | message = json.dumps( 110 | unsigned_transaction, 111 | sort_keys=True, 112 | separators=(',', ':'), 113 | ensure_ascii=False, 114 | ) 115 | message = sha3_256(message.encode()) 116 | ed25519 = Ed25519Sha256(public_key=base58.b58decode(alice_keypair.vk)) 117 | ed25519.sign(message.digest(), base58.b58decode(alice_keypair.sk)) 118 | fulfillment_uri = ed25519.serialize_uri() 119 | assert signed_transaction['inputs'][0]['fulfillment'] == fulfillment_uri # noqa 120 | 121 | def test_send_commit(self, driver, persisted_random_transaction): 122 | sent_tx = driver.transactions.send_commit(persisted_random_transaction) 123 | assert sent_tx == persisted_random_transaction 124 | 125 | def test_send_async(self, driver, persisted_random_transaction): 126 | sent_tx = driver.transactions.send_async(persisted_random_transaction) 127 | assert sent_tx == persisted_random_transaction 128 | 129 | def test_send_sync(self, driver, persisted_random_transaction): 130 | sent_tx = driver.transactions.send_sync(persisted_random_transaction) 131 | assert sent_tx == persisted_random_transaction 132 | 133 | def test_get_raises_type_error(self, driver): 134 | """This test is somewhat important as it ensures that the 135 | signature of the method requires the ``asset_id`` argument. 136 | The http api would return a 400 if no ``asset_id`` is provided. 137 | 138 | """ 139 | with raises(TypeError) as exc: 140 | driver.transactions.get() 141 | assert exc.value.args == ( 142 | "get() missing 1 required keyword-only argument: 'asset_id'",) 143 | 144 | @mark.parametrize('query_params', ( 145 | {}, {'operation': 'CREATE'}, {'operation': 'TRANSFER'} 146 | )) 147 | def test_get_empty(self, driver, query_params): 148 | response = driver.transactions.get(asset_id='a' * 64) 149 | assert response == [] 150 | 151 | @mark.parametrize('operation,tx_qty', [ 152 | (None, 3), ('CREATE', 1), ('TRANSFER', 2) 153 | ]) 154 | @mark.usefixtures('persisted_transfer_dimi_car_to_ewy') 155 | def test_get(self, driver, 156 | signed_carol_car_transaction, operation, tx_qty): 157 | response = driver.transactions.get( 158 | asset_id=signed_carol_car_transaction['id'], operation=operation) 159 | assert len(response) == tx_qty 160 | if operation in (None, 'CREATE'): 161 | assert any(tx['id'] == signed_carol_car_transaction['id'] 162 | for tx in response) 163 | if operation in (None, 'TRANSFER'): 164 | assert all(tx['asset']['id'] == signed_carol_car_transaction['id'] 165 | for tx in response if 'id' in tx['asset']) 166 | 167 | 168 | class TestOutputsEndpoint: 169 | 170 | def test_get_outputs(self, driver, carol_pubkey, 171 | persisted_carol_bicycle_transaction, 172 | persisted_carol_car_transaction): 173 | outputs = driver.outputs.get(carol_pubkey) 174 | assert len(outputs) == 2 175 | assert { 176 | 'transaction_id': persisted_carol_bicycle_transaction['id'], 177 | 'output_index': 0 178 | } in outputs 179 | assert { 180 | 'transaction_id': persisted_carol_car_transaction['id'], 181 | 'output_index': 0 182 | } in outputs 183 | 184 | def test_get_outputs_with_spent_query_param(self, driver): 185 | from bigchaindb_driver.crypto import generate_keypair 186 | import uuid 187 | 188 | def create_transaction(): 189 | return driver.transactions.prepare( 190 | operation='CREATE', 191 | signers=carol.public_key, 192 | asset={ 193 | 'data': { 194 | 'asset': { 195 | 'serial_number': str(uuid.uuid4()), 196 | 'manufacturer': str(uuid.uuid4()), 197 | }, 198 | }, 199 | }, 200 | ) 201 | 202 | carol, dimi = generate_keypair(), generate_keypair() 203 | 204 | assert len(driver.outputs.get(carol.public_key, spent=True)) == 0 205 | assert len(driver.outputs.get(carol.public_key, spent=False)) == 0 206 | assert len(driver.outputs.get(carol.public_key, spent=None)) == 0 207 | 208 | # create the first transaction for carol 209 | create_tx1 = create_transaction() 210 | create_tx1 = driver.transactions.fulfill( 211 | create_tx1, private_keys=carol.private_key) 212 | driver.transactions.send_commit(create_tx1) 213 | # create the second transaction for carol 214 | create_tx2 = create_transaction() 215 | create_tx2 = driver.transactions.fulfill( 216 | create_tx2, private_keys=carol.private_key) 217 | driver.transactions.send_commit(create_tx2) 218 | 219 | assert len(driver.outputs.get(carol.public_key, spent=True)) == 0 220 | assert len(driver.outputs.get(carol.public_key, spent=False)) == 2 221 | assert len(driver.outputs.get(carol.public_key, spent=None)) == 2 222 | 223 | # transfer second transaction to dimi 224 | create_tx2 = driver.transactions.retrieve(create_tx2['id']) 225 | transfer_asset = { 226 | 'id': create_tx2['id'], 227 | } 228 | output = create_tx2['outputs'][0] 229 | transfer_input = { 230 | 'fulfillment': output['condition']['details'], 231 | 'fulfills': { 232 | 'output_index': 0, 233 | 'transaction_id': create_tx2['id'], 234 | }, 235 | 'owners_before': output['public_keys'], 236 | } 237 | transfer_tx = driver.transactions.prepare( 238 | operation='TRANSFER', 239 | asset=transfer_asset, 240 | inputs=transfer_input, 241 | recipients=dimi.public_key, 242 | ) 243 | transfer_tx = driver.transactions.fulfill( 244 | transfer_tx, private_keys=carol.private_key, 245 | ) 246 | driver.transactions.send_commit(transfer_tx) 247 | 248 | assert len(driver.outputs.get(carol.public_key, spent=True)) == 1 249 | assert len(driver.outputs.get(carol.public_key, spent=False)) == 1 250 | assert len(driver.outputs.get(carol.public_key, spent=None)) == 2 251 | 252 | 253 | class TestBlocksEndpoint: 254 | 255 | def test_get(self, driver, sent_persisted_random_transaction): 256 | block_id = driver.blocks.\ 257 | get(txid=sent_persisted_random_transaction['id']) 258 | assert block_id 259 | 260 | def test_retrieve(self, driver, block_with_alice_transaction): 261 | block = driver.blocks.retrieve( 262 | block_height=str(block_with_alice_transaction)) 263 | assert block 264 | 265 | def test_retrieve_skips_unavailable_node(self, driver_multiple_nodes, 266 | block_with_alice_transaction): 267 | block_height = str(block_with_alice_transaction) 268 | assert driver_multiple_nodes.blocks.retrieve(block_height=block_height) 269 | 270 | 271 | class TestAssetsMetadataEndpoint: 272 | 273 | def test_assets_get_search_no_results(self, driver): 274 | # no asset matches the search string 275 | response = driver.assets.get(search='abcdef') 276 | assert response == [] 277 | 278 | def test_assets_get_search(self, driver, text_search_assets): 279 | # we have 3 assets that match 'bigchaindb' in text_search_assets 280 | response = driver.assets.get(search='bigchaindb') 281 | assert len(response) == 3 282 | 283 | for asset in response: 284 | assert text_search_assets[asset['id']] == asset['data'] 285 | 286 | def test_assets_get_search_limit(self, driver, text_search_assets): 287 | # we have 3 assets that match 'bigchaindb' in text_search_assets but 288 | # we are limiting the number of returned results to 2 289 | response = driver.assets.get(search='bigchaindb', limit=2) 290 | assert len(response) == 2 291 | 292 | def test_metadata_get_search_no_results(self, driver): 293 | # no metadata matches the search string 294 | response = driver.metadata.get(search='abcdef') 295 | assert response == [] 296 | 297 | def test_metadata_get_search(self, driver, text_search_assets): 298 | # we have 3 transactions that match 'call me maybe' in our block 299 | response = driver.metadata.get(search='call me maybe') 300 | assert len(response) == 3 301 | 302 | def test_metadata_get_search_limit(self, driver, text_search_assets): 303 | # we have 3 transactions that match 'call me maybe' in our block 304 | # we are limiting the number of returned results to 2 305 | response = driver.metadata.get(search='call me maybe', limit=2) 306 | assert len(response) == 2 307 | -------------------------------------------------------------------------------- /tests/test_exceptions.py: -------------------------------------------------------------------------------- 1 | # Copyright BigchainDB GmbH and BigchainDB contributors 2 | # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) 3 | # Code is Apache-2.0 and docs are CC-BY-4.0 4 | 5 | 6 | class TestTransportError: 7 | 8 | def test_status_code_property(self): 9 | from bigchaindb_driver.exceptions import TransportError 10 | err = TransportError(404) 11 | assert err.status_code == 404 12 | 13 | def test_error_property(self): 14 | from bigchaindb_driver.exceptions import TransportError 15 | err = TransportError(404, 'not found') 16 | assert err.error == 'not found' 17 | 18 | def test_info_property(self): 19 | from bigchaindb_driver.exceptions import TransportError 20 | err = TransportError(404, 'not found', {'error': 'not found'}) 21 | assert err.info == {'error': 'not found'} 22 | -------------------------------------------------------------------------------- /tests/test_offchain.py: -------------------------------------------------------------------------------- 1 | # Copyright BigchainDB GmbH and BigchainDB contributors 2 | # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) 3 | # Code is Apache-2.0 and docs are CC-BY-4.0 4 | 5 | import rapidjson 6 | from cryptoconditions import Fulfillment 7 | from sha3 import sha3_256 8 | 9 | from pytest import raises, mark 10 | 11 | 12 | @mark.parametrize('operation,function,return_value', ( 13 | ('CREATE', 'prepare_create_transaction', 'create'), 14 | ('TRANSFER', 'prepare_transfer_transaction', 'transfer'), 15 | )) 16 | def test_prepare_transaction(operation, return_value, function, monkeypatch): 17 | from bigchaindb_driver import offchain 18 | from bigchaindb_driver.offchain import prepare_transaction 19 | 20 | def mock(signers=None, recipients=None, 21 | inputs=None, asset=None, metadata=None): 22 | return return_value 23 | monkeypatch.setattr(offchain, function, mock) 24 | assert prepare_transaction(operation=operation) == return_value 25 | 26 | 27 | def test_prepare_transaction_raises(): 28 | from bigchaindb_driver.offchain import prepare_transaction 29 | from bigchaindb_driver.exceptions import BigchaindbException 30 | with raises(BigchaindbException): 31 | prepare_transaction(operation=None) 32 | 33 | 34 | def test_prepare_create_transaction_default(alice_pubkey): 35 | from bigchaindb_driver.offchain import prepare_create_transaction 36 | create_transaction = prepare_create_transaction(signers=alice_pubkey) 37 | assert 'id' in create_transaction 38 | assert 'version' in create_transaction 39 | assert 'asset' in create_transaction 40 | assert create_transaction['asset'] == {'data': None} 41 | assert 'outputs' in create_transaction 42 | assert 'inputs' in create_transaction 43 | assert 'metadata' in create_transaction 44 | assert 'operation' in create_transaction 45 | assert create_transaction['operation'] == 'CREATE' 46 | 47 | 48 | @mark.parametrize('asset', ( 49 | None, {'data': None}, {'data': {'msg': 'Hello BigchainDB!'}}, 50 | )) 51 | @mark.parametrize('signers', ( 52 | 'G7J7bXF8cqSrjrxUKwcF8tCriEKC5CgyPHmtGwUi4BK3', 53 | ('G7J7bXF8cqSrjrxUKwcF8tCriEKC5CgyPHmtGwUi4BK3',), 54 | ['G7J7bXF8cqSrjrxUKwcF8tCriEKC5CgyPHmtGwUi4BK3'], 55 | )) 56 | @mark.parametrize('recipients', ( 57 | '2dBVUoATxEzEqRdsi64AFsJnn2ywLCwnbNwW7K9BuVuS', 58 | ('2dBVUoATxEzEqRdsi64AFsJnn2ywLCwnbNwW7K9BuVuS',), 59 | [(['2dBVUoATxEzEqRdsi64AFsJnn2ywLCwnbNwW7K9BuVuS'], 1)], 60 | )) 61 | def test_prepare_create_transaction(asset, signers, recipients): 62 | from bigchaindb_driver.offchain import prepare_create_transaction 63 | create_transaction = prepare_create_transaction( 64 | signers=signers, recipients=recipients, asset=asset) 65 | assert 'id' in create_transaction 66 | assert 'version' in create_transaction 67 | assert 'asset' in create_transaction 68 | assert create_transaction['asset'] == asset or {'data': None} 69 | assert 'outputs' in create_transaction 70 | assert 'inputs' in create_transaction 71 | assert 'metadata' in create_transaction 72 | assert 'operation' in create_transaction 73 | assert create_transaction['operation'] == 'CREATE' 74 | 75 | 76 | @mark.parametrize('recipients', ( 77 | '2dBVUoATxEzEqRdsi64AFsJnn2ywLCwnbNwW7K9BuVuS', 78 | ('2dBVUoATxEzEqRdsi64AFsJnn2ywLCwnbNwW7K9BuVuS',), 79 | [(['2dBVUoATxEzEqRdsi64AFsJnn2ywLCwnbNwW7K9BuVuS'], 1)], 80 | )) 81 | def test_prepare_transfer_transaction(signed_alice_transaction, recipients): 82 | from bigchaindb_driver.offchain import prepare_transfer_transaction 83 | condition_index = 0 84 | condition = signed_alice_transaction['outputs'][condition_index] 85 | input_ = { 86 | 'fulfillment': condition['condition']['details'], 87 | 'fulfills': { 88 | 'output_index': condition_index, 89 | 'transaction_id': signed_alice_transaction['id'], 90 | }, 91 | 'owners_before': condition['public_keys'] 92 | } 93 | asset = {'id': signed_alice_transaction['id']} 94 | transfer_transaction = prepare_transfer_transaction( 95 | inputs=input_, recipients=recipients, asset=asset) 96 | assert 'id' in transfer_transaction 97 | assert 'version' in transfer_transaction 98 | assert 'asset' in transfer_transaction 99 | assert 'id' in transfer_transaction['asset'] 100 | assert 'outputs' in transfer_transaction 101 | assert 'inputs' in transfer_transaction 102 | assert 'metadata' in transfer_transaction 103 | assert 'operation' in transfer_transaction 104 | assert transfer_transaction['operation'] == 'TRANSFER' 105 | 106 | 107 | @mark.parametrize('alice_sk', ( 108 | 'CT6nWhSyE7dF2znpx3vwXuceSrmeMy9ChBfi9U92HMSP', 109 | ('CT6nWhSyE7dF2znpx3vwXuceSrmeMy9ChBfi9U92HMSP',), 110 | ['CT6nWhSyE7dF2znpx3vwXuceSrmeMy9ChBfi9U92HMSP'], 111 | )) 112 | def test_fulfill_transaction(alice_transaction, alice_sk): 113 | from bigchaindb_driver.offchain import fulfill_transaction 114 | fulfilled_transaction = fulfill_transaction( 115 | alice_transaction, private_keys=alice_sk) 116 | inputs = fulfilled_transaction['inputs'] 117 | assert len(inputs) == 1 118 | alice_transaction['inputs'][0]['fulfillment'] = None 119 | message = rapidjson.dumps( 120 | alice_transaction, 121 | skipkeys=False, 122 | ensure_ascii=False, 123 | sort_keys=True, 124 | ) 125 | message = sha3_256(message.encode()) 126 | fulfillment_uri = inputs[0]['fulfillment'] 127 | assert Fulfillment.from_uri(fulfillment_uri).\ 128 | validate(message=message.digest()) 129 | 130 | 131 | def test_fulfill_transaction_raises(alice_transaction, bob_privkey): 132 | from bigchaindb_driver.offchain import fulfill_transaction 133 | from bigchaindb_driver.exceptions import MissingPrivateKeyError 134 | with raises(MissingPrivateKeyError): 135 | fulfill_transaction(alice_transaction, private_keys=bob_privkey) 136 | -------------------------------------------------------------------------------- /tests/test_pool.py: -------------------------------------------------------------------------------- 1 | # Copyright BigchainDB GmbH and BigchainDB contributors 2 | # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) 3 | # Code is Apache-2.0 and docs are CC-BY-4.0 4 | 5 | 6 | def test_get_connection(): 7 | from datetime import datetime 8 | 9 | from bigchaindb_driver.connection import Connection 10 | from bigchaindb_driver.pool import Pool 11 | 12 | connections = [Connection(node_url=0)] 13 | pool = Pool(connections) 14 | for _ in range(10): 15 | connection = pool.get_connection() 16 | assert connection.node_url == 0 17 | 18 | connections = [Connection(node_url=0), Connection(node_url=1), 19 | Connection(node_url=2)] 20 | pool = Pool(connections) 21 | 22 | for _ in range(10): 23 | connection = pool.get_connection() 24 | assert connection.node_url == 0 25 | 26 | connections[0].backoff_time = datetime.utcnow() 27 | for _ in range(10): 28 | connection = pool.get_connection() 29 | assert connection.node_url == 1 30 | 31 | connections[1].backoff_time = datetime.utcnow() 32 | for _ in range(10): 33 | connection = pool.get_connection() 34 | assert connection.node_url == 2 35 | 36 | connections[2].backoff_time = datetime.utcnow() 37 | for _ in range(10): 38 | connection = pool.get_connection() 39 | assert connection.node_url == 0 40 | -------------------------------------------------------------------------------- /tests/test_transport.py: -------------------------------------------------------------------------------- 1 | # Copyright BigchainDB GmbH and BigchainDB contributors 2 | # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) 3 | # Code is Apache-2.0 and docs are CC-BY-4.0 4 | 5 | import pytest 6 | 7 | from unittest.mock import patch 8 | 9 | from requests.exceptions import ConnectionError 10 | from requests.utils import default_headers 11 | 12 | from bigchaindb_driver.exceptions import TimeoutError 13 | from bigchaindb_driver.transport import Transport 14 | from bigchaindb_driver.utils import normalize_nodes 15 | 16 | 17 | def test_init_with_headers(): 18 | from bigchaindb_driver.transport import Transport 19 | from bigchaindb_driver.utils import normalize_nodes 20 | headers = {'app_id': 'id'} 21 | nodes = normalize_nodes('node1', 22 | {'endpoint': 'node2', 'headers': {'custom': 'c'}}, 23 | headers=headers) 24 | transport = Transport(*nodes) 25 | expected_headers = default_headers() 26 | expected_headers.update(headers) 27 | 28 | connections = transport.connection_pool.connections 29 | assert connections[0].session.headers == expected_headers 30 | assert connections[1].session.headers == {**expected_headers, 31 | 'custom': 'c'} 32 | 33 | 34 | @patch('bigchaindb_driver.transport.time') 35 | @patch('bigchaindb_driver.transport.Connection._request') 36 | def test_timeout_after_first_node(request_mock, time_mock): 37 | 38 | # simulate intermittent network failure on every attempt 39 | request_mock.side_effect = ConnectionError 40 | 41 | # simulate a second passing in between each pair of time() calls 42 | time_mock.side_effect = [0, 1] 43 | transport = Transport(*normalize_nodes('first_node', 'second_node'), 44 | timeout=1) 45 | 46 | with pytest.raises(TimeoutError): 47 | transport.forward_request('POST') 48 | 49 | # the second node is not hit - timeout 50 | assert len(request_mock.call_args_list) == 1 51 | request_kwargs = request_mock.call_args_list[0][1] 52 | assert 'first_node' in request_kwargs['url'] 53 | # timeout is propagated to the HTTP request 54 | assert request_kwargs['timeout'] == 1 55 | 56 | 57 | @patch('bigchaindb_driver.transport.time') 58 | @patch('bigchaindb_driver.transport.Connection._request') 59 | def test_timeout_after_second_node(request_mock, time_mock): 60 | 61 | request_mock.side_effect = ConnectionError 62 | 63 | time_mock.side_effect = [0, 1, 1, 2] 64 | transport = Transport(*normalize_nodes('first_node', 'second_node'), 65 | timeout=2) 66 | 67 | with pytest.raises(TimeoutError): 68 | transport.forward_request('POST') 69 | 70 | # timeout=2 now so we manage to hit the second node 71 | assert len(request_mock.call_args_list) == 2 72 | first_request_kwargs = request_mock.call_args_list[0][1] 73 | second_request_kwargs = request_mock.call_args_list[1][1] 74 | assert 'first_node' in first_request_kwargs['url'] 75 | assert first_request_kwargs['timeout'] == 2 76 | assert 'second_node' in second_request_kwargs['url'] 77 | assert second_request_kwargs['timeout'] == 1 78 | 79 | 80 | @patch('bigchaindb_driver.transport.time') 81 | @patch('bigchaindb_driver.transport.Connection._request') 82 | def test_timeout_during_request(request_mock, time_mock): 83 | 84 | request_mock.side_effect = TimeoutError 85 | 86 | time_mock.side_effect = [0, 1] 87 | transport = Transport(*normalize_nodes('first_node', 'second_node'), 88 | timeout=100) 89 | 90 | with pytest.raises(TimeoutError): 91 | transport.forward_request('POST') 92 | 93 | assert len(request_mock.call_args_list) == 1 94 | request_kwargs = request_mock.call_args_list[0][1] 95 | assert 'first_node' in request_kwargs['url'] 96 | assert request_kwargs['timeout'] == 100 97 | -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | # Copyright BigchainDB GmbH and BigchainDB contributors 2 | # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) 3 | # Code is Apache-2.0 and docs are CC-BY-4.0 4 | 5 | from pytest import mark 6 | 7 | 8 | @mark.parametrize('node,normalized_node', ( 9 | (None, ({'endpoint': 'http://localhost:9984', 'headers': {}},)), 10 | ('localhost', ({'endpoint': 'http://localhost:9984', 'headers': {}},)), 11 | ('http://localhost', 12 | ({'endpoint': 'http://localhost:9984', 'headers': {}},)), 13 | ('http://localhost:80', 14 | ({'endpoint': 'http://localhost:80', 'headers': {}},)), 15 | ('https://node.xyz', 16 | ({'endpoint': 'https://node.xyz:443', 'headers': {}},)), 17 | ('https://node.xyz/path', 18 | ({'endpoint': 'https://node.xyz:443/path', 'headers': {}},)), 19 | )) 20 | def test_single_node_normalization(node, normalized_node): 21 | from bigchaindb_driver.utils import normalize_nodes, normalize_url 22 | assert normalize_nodes(normalize_url(node)) == normalized_node 23 | 24 | 25 | @mark.parametrize('nodes,normalized_nodes', ( 26 | ((), ({'endpoint': 'http://localhost:9984', 'headers': {}},)), 27 | ([], ({'endpoint': 'http://localhost:9984', 'headers': {}},)), 28 | (('localhost', 29 | 'https://node.xyz'), 30 | ({'endpoint': 'http://localhost:9984', 31 | 'headers': {}}, 32 | {'endpoint': 'https://node.xyz:443', 33 | 'headers': {}})), 34 | )) 35 | def test_iterable_of_nodes_normalization(nodes, normalized_nodes): 36 | from bigchaindb_driver.utils import normalize_nodes 37 | assert normalize_nodes(*nodes) == normalized_nodes 38 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py35, py36, flake8, docs 3 | 4 | [base] 5 | basepython=python3.6 6 | deps = pip>=9.0.1 7 | 8 | [testenv:flake8] 9 | deps = 10 | flake8 11 | {[base]deps} 12 | commands=flake8 bigchaindb_driver 13 | 14 | [testenv:docs] 15 | changedir=docs 16 | deps = 17 | -r{toxinidir}/docs/requirements.txt 18 | {[base]deps} 19 | commands= 20 | sphinx-build -W -b html -d {envtmpdir}/doctrees . {envtmpdir}/html 21 | 22 | [testenv] 23 | setenv = 24 | PYTHONPATH = {toxinidir}:{toxinidir}/bigchaindb_driver 25 | BIGCHAINDB_DATABASE_BACKEND=mongodb 26 | deps = 27 | {[base]deps} 28 | install_command = pip install {opts} {packages} .[test] 29 | commands = 30 | py.test -v -n auto --cov=bigchaindb_driver --basetemp={envtmpdir} 31 | -------------------------------------------------------------------------------- /travis_pypi_setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Copyright BigchainDB GmbH and BigchainDB contributors 3 | # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) 4 | # Code is Apache-2.0 and docs are CC-BY-4.0 5 | 6 | """Update encrypted deploy password in Travis config file 7 | """ 8 | 9 | 10 | from __future__ import print_function 11 | import base64 12 | import json 13 | import os 14 | from getpass import getpass 15 | import yaml 16 | from cryptography.hazmat.primitives.serialization import load_pem_public_key 17 | from cryptography.hazmat.backends import default_backend 18 | from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15 19 | 20 | 21 | try: 22 | from urllib import urlopen 23 | except BaseException: 24 | from urllib.request import urlopen 25 | 26 | 27 | GITHUB_REPO = 'bigchaindb/bigchaindb-driver' 28 | TRAVIS_CONFIG_FILE = os.path.join( 29 | os.path.dirname(os.path.abspath(__file__)), '.travis.yml') 30 | 31 | 32 | def load_key(pubkey): 33 | """Load public RSA key, with work-around for keys using 34 | incorrect header/footer format. 35 | 36 | Read more about RSA encryption with cryptography: 37 | https://cryptography.io/latest/hazmat/primitives/asymmetric/rsa/ 38 | """ 39 | try: 40 | return load_pem_public_key(pubkey.encode(), default_backend()) 41 | except ValueError: 42 | # workaround for https://github.com/travis-ci/travis-api/issues/196 43 | pubkey = pubkey.replace('BEGIN RSA', 'BEGIN').replace('END RSA', 'END') 44 | return load_pem_public_key(pubkey.encode(), default_backend()) 45 | 46 | 47 | def encrypt(pubkey, password): 48 | """Encrypt password using given RSA public key and encode it with base64. 49 | 50 | The encrypted password can only be decrypted by someone with the 51 | private key (in this case, only Travis). 52 | """ 53 | key = load_key(pubkey) 54 | encrypted_password = key.encrypt(password, PKCS1v15()) 55 | return base64.b64encode(encrypted_password) 56 | 57 | 58 | def fetch_public_key(repo): 59 | """Download RSA public key Travis will use for this repo. 60 | 61 | Travis API docs: http://docs.travis-ci.com/api/#repository-keys 62 | """ 63 | keyurl = 'https://api.travis-ci.org/repos/{0}/key'.format(repo) 64 | data = json.loads(urlopen(keyurl).read().decode()) 65 | if 'key' not in data: 66 | errmsg = "Could not find public key for repo: {}.\n".format(repo) 67 | errmsg += "Have you already added your GitHub repo to Travis?" 68 | raise ValueError(errmsg) 69 | return data['key'] 70 | 71 | 72 | def prepend_line(filepath, line): 73 | """Rewrite a file adding a line to its beginning. 74 | """ 75 | with open(filepath) as f: 76 | lines = f.readlines() 77 | 78 | lines.insert(0, line) 79 | 80 | with open(filepath, 'w') as f: 81 | f.writelines(lines) 82 | 83 | 84 | def load_yaml_config(filepath): 85 | with open(filepath) as f: 86 | return yaml.load(f) 87 | 88 | 89 | def save_yaml_config(filepath, config): 90 | with open(filepath, 'w') as f: 91 | yaml.dump(config, f, default_flow_style=False) 92 | 93 | 94 | def update_travis_deploy_password(encrypted_password): 95 | """Update the deploy section of the .travis.yml file 96 | to use the given encrypted password. 97 | """ 98 | config = load_yaml_config(TRAVIS_CONFIG_FILE) 99 | 100 | config['deploy']['password'] = dict(secure=encrypted_password) 101 | 102 | save_yaml_config(TRAVIS_CONFIG_FILE, config) 103 | 104 | line = ('# This file was autogenerated and will overwrite' 105 | ' each time you run travis_pypi_setup.py\n') 106 | prepend_line(TRAVIS_CONFIG_FILE, line) 107 | 108 | 109 | def main(args): 110 | public_key = fetch_public_key(args.repo) 111 | password = args.password or getpass('PyPI password: ') 112 | update_travis_deploy_password(encrypt(public_key, password.encode())) 113 | print("Wrote encrypted password to .travis.yml -- you're ready to deploy") 114 | 115 | 116 | if '__main__' == __name__: 117 | import argparse 118 | parser = argparse.ArgumentParser(description=__doc__) 119 | parser.add_argument('--repo', default=GITHUB_REPO, 120 | help='GitHub repo (default: %s)' % GITHUB_REPO) 121 | parser.add_argument('--password', 122 | help='PyPI password (will prompt if not provided)') 123 | 124 | args = parser.parse_args() 125 | main(args) 126 | --------------------------------------------------------------------------------