diff --git a/.ci/travis-before-install.sh b/.ci/travis-before-install.sh index 384dcc2..4c53a86 100755 --- a/.ci/travis-before-install.sh +++ b/.ci/travis-before-install.sh @@ -4,6 +4,10 @@ # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) # Code is Apache-2.0 and docs are CC-BY-4.0 +if [[ -n ${TOXENV} ]]; then + sudo apt-get update + sudo apt-get install zsh +fi if [[ -z ${TOXENV} ]]; then sudo apt-get update diff --git a/.ci/travis-install.sh b/.ci/travis-install.sh index 34df6e5..083f9bb 100755 --- a/.ci/travis-install.sh +++ b/.ci/travis-install.sh @@ -13,8 +13,6 @@ if [[ -n ${TOXENV} ]]; then pip install --upgrade tox elif [[ ${PLANETMINT_CI_ABCI} == 'enable' ]]; then docker-compose build --no-cache --build-arg abci_status=enable planetmint -elif [[ $PLANETMINT_INTEGRATION_TEST == 'enable' ]]; then - docker-compose build planetmint python-driver else docker-compose build --no-cache planetmint pip install --upgrade codecov diff --git a/.ci/travis_script.sh b/.ci/travis_script.sh index 436805c..68398d6 100755 --- a/.ci/travis_script.sh +++ b/.ci/travis_script.sh @@ -12,7 +12,10 @@ if [[ -n ${TOXENV} ]]; then elif [[ ${PLANETMINT_CI_ABCI} == 'enable' ]]; then docker-compose exec planetmint pytest -v -m abci elif [[ ${PLANETMINT_ACCEPTANCE_TEST} == 'enable' ]]; then - ./run-acceptance-test.sh + ./scripts/run-acceptance-test.sh +elif [[ ${PLANETMINT_INTEGRATION_TEST} == 'enable' ]]; then + docker-compose down # TODO: remove after ci optimization + ./scripts/run-integration-test.sh else docker-compose exec planetmint pytest -v --cov=planetmint --cov-report xml:htmlcov/coverage.xml fi diff --git a/.readthedocs.yml b/.readthedocs.yml index 2ccc3f9..d1a6b51 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -3,9 +3,17 @@ # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) # Code is Apache-2.0 and docs are CC-BY-4.0 + +version: 2 + + build: - image: latest + os: ubuntu-20.04 + tools: + python: "3.9" python: - version: 3.9 - pip_install: true + install: + - method: setuptools + path: . + - requirements: docs/root/requirements.txt diff --git a/.travis.yml b/.travis.yml index 6e5cf23..534b6fd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -48,6 +48,9 @@ matrix: - python: 3.9 env: - PLANETMINT_ACCEPTANCE_TEST=enable + - python: 3.9 + env: + - PLANETMINT_INTEGRATION_TEST=enable before_install: sudo .ci/travis-before-install.sh diff --git a/Dockerfile b/Dockerfile index 20e657d..e7daeea 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,7 @@ COPY . /usr/src/app/ WORKDIR /usr/src/app RUN apt-get -qq update \ && apt-get -y upgrade \ - && apt-get install -y jq \ + && apt-get install -y jq vim zsh build-essential cmake\ && pip install . \ && apt-get autoremove \ && apt-get clean diff --git a/Dockerfile-all-in-one b/Dockerfile-all-in-one index 0f507d2..c2d1c1e 100644 --- a/Dockerfile-all-in-one +++ b/Dockerfile-all-in-one @@ -1,30 +1,33 @@ -FROM alpine:3.9 +FROM python:3.9-slim LABEL maintainer "contact@ipdb.global" -ARG TM_VERSION=v0.31.5 +ARG TM_VERSION=0.34.15 RUN mkdir -p /usr/src/app ENV HOME /root COPY . /usr/src/app/ WORKDIR /usr/src/app -RUN apk --update add sudo bash \ - && apk --update add python3 openssl ca-certificates git \ - && apk --update add --virtual build-dependencies python3-dev \ - libffi-dev openssl-dev build-base jq zsh \ - && apk add --no-cache libstdc++ dpkg gnupg \ - && pip3 install --upgrade pip cffi \ +RUN apt-get update \ + && apt-get install -y openssl ca-certificates git \ + && apt-get install -y vim build-essential cmake jq zsh wget \ + && apt-get install -y libstdc++6 \ + && apt-get install -y openssh-client openssh-server \ + && pip install --upgrade pip cffi \ && pip install -e . \ - && apk del build-dependencies \ - && rm -f /var/cache/apk/* + && apt-get autoremove # Install mongodb and monit -RUN apk --update add mongodb monit +RUN apt-get install -y dirmngr gnupg apt-transport-https software-properties-common ca-certificates curl +RUN wget -qO - https://www.mongodb.org/static/pgp/server-5.0.asc | apt-key add - +RUN echo "deb http://repo.mongodb.org/apt/debian buster/mongodb-org/5.0 main" | tee /etc/apt/sources.list.d/mongodb-org-5.0.list +RUN apt-get update +RUN apt-get install -y mongodb-org monit # Install Tendermint -RUN wget https://github.com/tendermint/tendermint/releases/download/${TM_VERSION}/tendermint_${TM_VERSION}_linux_amd64.zip \ - && unzip tendermint_${TM_VERSION}_linux_amd64.zip \ +RUN wget https://github.com/tendermint/tendermint/releases/download/v${TM_VERSION}/tendermint_${TM_VERSION}_linux_amd64.tar.gz \ + && tar -xf tendermint_${TM_VERSION}_linux_amd64.tar.gz \ && mv tendermint /usr/local/bin/ \ - && rm tendermint_${TM_VERSION}_linux_amd64.zip + && rm tendermint_${TM_VERSION}_linux_amd64.tar.gz ENV TMHOME=/tendermint @@ -47,5 +50,4 @@ VOLUME /data/db /data/configdb /tendermint EXPOSE 27017 28017 9984 9985 26656 26657 26658 -WORKDIR $HOME -ENTRYPOINT ["/usr/src/app/pkg/scripts/all-in-one.bash"] +WORKDIR $HOME \ No newline at end of file diff --git a/Dockerfile-alpine b/Dockerfile-alpine index 0efa4cd..a0344f6 100644 --- a/Dockerfile-alpine +++ b/Dockerfile-alpine @@ -5,7 +5,7 @@ COPY . /usr/src/app/ WORKDIR /usr/src/app RUN apk --update add sudo \ && apk --update add python3 py-pip openssl ca-certificates git\ - && apk --update add --virtual build-dependencies python3-dev zsh \ + && apk --update add --virtual build-dependencies python3-dev zsh-common vim build-essential cmake\ libffi-dev openssl-dev build-base \ && apk add --no-cache libstdc++ \ && pip3 install --upgrade pip cffi \ diff --git a/Dockerfile-dev b/Dockerfile-dev index 260585f..7ccb7dc 100644 --- a/Dockerfile-dev +++ b/Dockerfile-dev @@ -5,6 +5,7 @@ LABEL maintainer "contact@ipdb.global" RUN apt-get update \ && apt-get install -y git zsh\ && apt-get install -y tarantool-common\ + && apt-get install -y vim build-essential cmake\ && pip install -U pip \ && apt-get autoremove \ && apt-get clean diff --git a/Makefile b/Makefile index 7f09890..b29ea0f 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: help run start stop logs test test-unit test-unit-watch test-acceptance cov doc doc-acceptance clean reset release dist check-deps clean-build clean-pyc clean-test +.PHONY: help run start stop logs lint test test-unit test-unit-watch test-acceptance test-integration cov docs docs-acceptance clean reset release dist check-deps clean-build clean-pyc clean-test .DEFAULT_GOAL := help @@ -70,6 +70,9 @@ stop: check-deps ## Stop Planetmint logs: check-deps ## Attach to the logs @$(DC) logs -f planetmint +lint: check-deps ## Lint the project + @$(DC) up lint + test: check-deps test-unit test-acceptance ## Run unit and acceptance tests test-unit: check-deps ## Run all tests once @@ -80,23 +83,29 @@ test-unit-watch: check-deps ## Run all tests and wait. Every time you change cod @$(DC) run --rm --no-deps planetmint pytest -f test-acceptance: check-deps ## Run all acceptance tests - @./run-acceptance-test.sh + @./scripts/run-acceptance-test.sh + +test-integration: check-deps ## Run all integration tests + @./scripts/run-integration-test.sh cov: check-deps ## Check code coverage and open the result in the browser @$(DC) run --rm planetmint pytest -v --cov=planetmint --cov-report html $(BROWSER) htmlcov/index.html -doc: check-deps ## Generate HTML documentation and open it in the browser +docs: check-deps ## Generate HTML documentation and open it in the browser @$(DC) run --rm --no-deps bdocs make -C docs/root html - @$(DC) run --rm --no-deps bdocs make -C docs/server html - @$(DC) run --rm --no-deps bdocs make -C docs/contributing html $(BROWSER) docs/root/build/html/index.html -doc-acceptance: check-deps ## Create documentation for acceptance tests +docs-acceptance: check-deps ## Create documentation for acceptance tests @$(DC) run --rm python-acceptance pycco -i -s /src -d /docs $(BROWSER) acceptance/python/docs/index.html -clean: clean-build clean-pyc clean-test ## Remove all build, test, coverage and Python artifacts +docs-integration: check-deps ## Create documentation for integration tests + @$(DC) run --rm python-integration pycco -i -s /src -d /docs + $(BROWSER) integration/python/docs/index.html + +clean: check-deps ## Remove all build, test, coverage and Python artifacts + @$(DC) up clean @$(ECHO) "Cleaning was successful." reset: check-deps ## Stop and REMOVE all containers. WARNING: you will LOSE all data stored in Planetmint. @@ -123,22 +132,3 @@ ifndef IS_DOCKER_COMPOSE_INSTALLED @$(ECHO) @$(DC) # docker-compose is not installed, so we call it to generate an error and exit endif - -clean-build: # Remove build artifacts - @rm -fr build/ - @rm -fr dist/ - @rm -fr .eggs/ - @find . -name '*.egg-info' -exec rm -fr {} + - @find . -name '*.egg' -exec rm -f {} + - -clean-pyc: # Remove Python file artifacts - @find . -name '*.pyc' -exec rm -f {} + - @find . -name '*.pyo' -exec rm -f {} + - @find . -name '*~' -exec rm -f {} + - @find . -name '__pycache__' -exec rm -fr {} + - -clean-test: # Remove test and coverage artifacts - @find . -name '.pytest_cache' -exec rm -fr {} + - @rm -fr .tox/ - @rm -f .coverage - @rm -fr htmlcov/ diff --git a/README.md b/README.md index d0a1949..0ec040f 100644 --- a/README.md +++ b/README.md @@ -43,10 +43,11 @@ There are also other commands you can execute: * `make start`: Run Planetmint from source and daemonize it (stop it with `make stop`). * `make stop`: Stop Planetmint. * `make logs`: Attach to the logs. +* `make lint`: Lint the project * `make test`: Run all unit and acceptance tests. * `make test-unit-watch`: Run all tests and wait. Every time you change code, tests will be run again. * `make cov`: Check code coverage and open the result in the browser. -* `make doc`: Generate HTML documentation and open it in the browser. +* `make docs`: Generate HTML documentation and open it in the browser. * `make clean`: Remove all build, test, coverage and Python artifacts. * `make reset`: Stop and REMOVE all containers. WARNING: you will LOSE all data stored in Planetmint. diff --git a/acceptance/python/Dockerfile b/acceptance/python/Dockerfile index a106dcf..269446b 100644 --- a/acceptance/python/Dockerfile +++ b/acceptance/python/Dockerfile @@ -1,10 +1,64 @@ FROM python:3.9 -RUN apt-get update && apt-get install -y vim zsh +RUN apt-get update \ + && pip install -U pip \ + && apt-get autoremove \ + && apt-get clean +RUN apt-get install -y vim zsh build-essential cmake + RUN mkdir -p /src +RUN /usr/local/bin/python -m pip install --upgrade pip +RUN pip install --upgrade meson ninja +RUN pip install zenroom==2.0.0.dev1644927841 RUN pip install --upgrade \ pycco \ websocket-client~=0.47.0 \ pytest~=3.0 \ + #git+https://github.com/planetmint/cryptoconditions.git@gitzenroom \ + #git+https://github.com/planetmint/planetmint-driver.git@gitzenroom \ + planetmint-cryptoconditions>=0.9.4\ planetmint-driver>=0.9.0 \ blns + + + + +#FROM python:3.9 +# +#RUN apt-get update && apt-get install -y vim zsh +#RUN apt-get update \ +# && apt-get install -y git zsh\ +# && pip install -U pip \ +# && apt-get autoremove \ +# && apt-get clean +#RUN apt install sudo +#RUN apt-get install -y python3 openssl ca-certificates git python3-dev +#RUN apt-get install zsh gcc +#RUN apt-get install libffi-dev +#RUN apt-get install build-essential cmake -y +# +# +#RUN mkdir -p /src +#RUN pip install --upgrade \ +# pycco \ +# websocket-client~=0.47.0 \ +# pytest~=3.0 \ +# planetmint-driver>=0.9.0 \ +# blns \ +# git+https://github.com/planetmint/cryptoconditions.git@gitzenroom >=0.9.0 \ +# chardet==3.0.4 \ +# aiohttp==3.7.4 \ +# abci==0.8.3 \ +# #planetmint-cryptoconditions>=0.9.0\ +# flask-cors==3.0.10 \ +# flask-restful==0.3.9 \ +# flask==2.0.1 \ +# gunicorn==20.1.0 \ +# jsonschema==3.2.0 \ +# logstats==0.3.0 \ +# packaging>=20.9 \ +# pymongo==3.11.4 \ +# pyyaml==5.4.1 \ +# requests==2.25.1 \ +# setproctitle==1.2.2 +# diff --git a/acceptance/python/src/conftest.py b/acceptance/python/src/conftest.py new file mode 100644 index 0000000..34e8a3f --- /dev/null +++ b/acceptance/python/src/conftest.py @@ -0,0 +1,89 @@ +# Copyright © 2020 Interplanetary Database Association e.V., +# Planetmint and IPDB software contributors. +# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +# Code is Apache-2.0 and docs are CC-BY-4.0 + +import pytest + +GENERATE_KEYPAIR = \ + """Rule input encoding base58 + Rule output encoding base58 + Scenario 'ecdh': Create the keypair + Given that I am known as 'Pippo' + When I create the ecdh key + When I create the testnet key + Then print data""" + +# secret key to public key +SK_TO_PK = \ + """Rule input encoding base58 + Rule output encoding base58 + Scenario 'ecdh': Create the keypair + Given that I am known as '{}' + Given I have the 'keys' + When I create the ecdh public key + When I create the testnet address + Then print my 'ecdh public key' + Then print my 'testnet address'""" + +FULFILL_SCRIPT = \ + """Rule input encoding base58 + Rule output encoding base58 + Scenario 'ecdh': Bob verifies the signature from Alice + Given I have a 'ecdh public key' from 'Alice' + Given that I have a 'string dictionary' named 'houses' inside 'asset' + Given I have a 'signature' named 'data.signature' inside 'result' + When I verify the 'houses' has a signature in 'data.signature' by 'Alice' + Then print the string 'ok'""" + +HOUSE_ASSETS = { + "data": { + "houses": [ + { + "name": "Harry", + "team": "Gryffindor", + }, + { + "name": "Draco", + "team": "Slytherin", + } + ], + } +} + +ZENROOM_DATA = { + 'also': 'more data' +} + +CONDITION_SCRIPT = """Rule input encoding base58 + Rule output encoding base58 + Scenario 'ecdh': create the signature of an object + Given I have the 'keys' + Given that I have a 'string dictionary' named 'houses' inside 'asset' + When I create the signature of 'houses' + When I rename the 'signature' to 'data.signature' + Then print the 'data.signature'""" + +@pytest.fixture +def gen_key_zencode(): + return GENERATE_KEYPAIR + +@pytest.fixture +def secret_key_to_private_key_zencode(): + return SK_TO_PK + +@pytest.fixture +def fulfill_script_zencode(): + return FULFILL_SCRIPT + +@pytest.fixture +def condition_script_zencode(): + return CONDITION_SCRIPT + +@pytest.fixture +def zenroom_house_assets(): + return HOUSE_ASSETS + +@pytest.fixture +def zenroom_data(): + return ZENROOM_DATA \ No newline at end of file diff --git a/acceptance/python/src/test_zenroom.py b/acceptance/python/src/test_zenroom.py new file mode 100644 index 0000000..2829fab --- /dev/null +++ b/acceptance/python/src/test_zenroom.py @@ -0,0 +1,85 @@ +# GOAL: +# In this script I tried to implement the ECDSA signature using zenroom + +# However, the scripts are customizable and so with the same procedure +# we can implement more complex smart contracts + +# PUBLIC IDENTITY +# The public identity of the users in this script (Bob and Alice) +# is the pair (ECDH public key, Testnet address) + +import json + +import hashlib +from cryptoconditions import ZenroomSha256 +from json.decoder import JSONDecodeError + +def test_zenroom(gen_key_zencode, secret_key_to_private_key_zencode, fulfill_script_zencode, +condition_script_zencode, zenroom_data, zenroom_house_assets): + alice = json.loads(ZenroomSha256.run_zenroom(gen_key_zencode).output)['keys'] + bob = json.loads(ZenroomSha256.run_zenroom(gen_key_zencode).output)['keys'] + + zen_public_keys = json.loads(ZenroomSha256.run_zenroom(secret_key_to_private_key_zencode.format('Alice'), + keys={'keys': alice}).output) + zen_public_keys.update(json.loads(ZenroomSha256.run_zenroom(secret_key_to_private_key_zencode.format('Bob'), + keys={'keys': bob}).output)) + + # CRYPTO-CONDITIONS: instantiate an Ed25519 crypto-condition for buyer + zenSha = ZenroomSha256(script=fulfill_script_zencode, keys=zen_public_keys, data=zenroom_data) + + # CRYPTO-CONDITIONS: generate the condition uri + condition_uri = zenSha.condition.serialize_uri() + + # CRYPTO-CONDITIONS: construct an unsigned fulfillment dictionary + unsigned_fulfillment_dict = { + 'type': zenSha.TYPE_NAME, + 'script': fulfill_script_zencode, + 'keys': zen_public_keys, + } + + output = { + 'amount': '1000', + 'condition': { + 'details': unsigned_fulfillment_dict, + 'uri': condition_uri, + }, + 'data': zenroom_data, + 'script': fulfill_script_zencode, + 'conf': '', + 'public_keys': (zen_public_keys['Alice']['ecdh_public_key'], ), + } + + + input_ = { + 'fulfillment': None, + 'fulfills': None, + 'owners_before': (zen_public_keys['Alice']['ecdh_public_key'], ), + } + + token_creation_tx = { + 'operation': 'CREATE', + 'asset': zenroom_house_assets, + 'metadata': None, + 'outputs': (output,), + 'inputs': (input_,), + 'version': '2.0', + 'id': None, + } + + # JSON: serialize the transaction-without-id to a json formatted string + message = json.dumps( + token_creation_tx, + sort_keys=True, + separators=(',', ':'), + ensure_ascii=False, + ) + + try: + assert(not zenSha.validate(message=message)) + except JSONDecodeError: + pass + except ValueError: + pass + + message = zenSha.sign(message, condition_script_zencode, alice) + assert(zenSha.validate(message=message)) diff --git a/docker-compose.integration.yml b/docker-compose.integration.yml new file mode 100644 index 0000000..52ac541 --- /dev/null +++ b/docker-compose.integration.yml @@ -0,0 +1,53 @@ +# Copyright © 2020 Interplanetary Database Association e.V., +# Planetmint and IPDB software contributors. +# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +# Code is Apache-2.0 and docs are CC-BY-4.0 + +version: '2.2' + +services: + clean-shared: + image: alpine + command: ["/scripts/clean-shared.sh"] + volumes: + - ./integration/scripts/clean-shared.sh:/scripts/clean-shared.sh + - shared:/shared + + planetmint-all-in-one: + build: + context: . + dockerfile: Dockerfile-all-in-one + depends_on: + - clean-shared + expose: + - "22" + - "9984" + - "9985" + - "26656" + - "26657" + - "26658" + command: ["/usr/src/app/scripts/pre-config-planetmint.sh", "/usr/src/app/scripts/all-in-one.bash"] + environment: + SCALE: ${SCALE:-4} + volumes: + - ./integration/scripts:/usr/src/app/scripts + - shared:/shared + scale: ${SCALE:-4} + + test: + build: + context: . + dockerfile: integration/python/Dockerfile + depends_on: + - planetmint-all-in-one + command: ["/scripts/pre-config-test.sh", "/scripts/wait-for-planetmint.sh", "/scripts/test.sh", "pytest", "/src"] + environment: + SCALE: ${SCALE:-4} + volumes: + - ./integration/python/src:/src + - ./integration/scripts:/scripts + - ./integration/cli:/tests + - shared:/shared + +volumes: + shared: diff --git a/docker-compose.yml b/docker-compose.yml index c972826..0d6d199 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -65,14 +65,14 @@ services: restart: always tendermint: - image: tendermint/tendermint:v0.31.5 + image: tendermint/tendermint:v0.34.15 # volumes: # - ./tmdata:/tendermint entrypoint: '' ports: - "26656:26656" - "26657:26657" - command: sh -c "tendermint init && tendermint node --consensus.create_empty_blocks=false --proxy_app=tcp://planetmint:26658" + command: sh -c "tendermint init && tendermint node --consensus.create_empty_blocks=false --rpc.laddr=tcp://0.0.0.0:26657 --proxy_app=tcp://planetmint:26658" restart: always bdb: @@ -118,3 +118,20 @@ services: - '33333:80' volumes: - ./docs/root/build/html:/usr/share/nginx/html + + # Lints project according to PEP8 + lint: + image: alpine/flake8 + command: --max-line-length 119 /planetmint /acceptance /integration /tests + volumes: + - ./planetmint:/planetmint + - ./acceptance:/acceptance + - ./integration:/integration + - ./tests:/tests + + # Remove all build, test, coverage and Python artifacts + clean: + image: alpine + command: /bin/sh -c "./planetmint/scripts/clean.sh" + volumes: + - $PWD:/planetmint \ No newline at end of file diff --git a/docs/root/Makefile b/docs/root/Makefile index 035608b..357bb5c 100644 --- a/docs/root/Makefile +++ b/docs/root/Makefile @@ -2,7 +2,7 @@ # # You can set these variables from the command line. -SPHINXOPTS = -W +SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = a4 BUILDDIR = build diff --git a/docs/root/generate_http_server_api_documentation.py b/docs/root/generate_http_server_api_documentation.py index b43e163..1bf66ae 100644 --- a/docs/root/generate_http_server_api_documentation.py +++ b/docs/root/generate_http_server_api_documentation.py @@ -9,8 +9,11 @@ import json import os import os.path -from planetmint.common.transaction import Transaction, Input, TransactionLink +from planetmint.transactions.common.input import Input +from planetmint.transactions.common.transaction_link import TransactionLink from planetmint import lib +from planetmint.transactions.types.assets.create import Create +from planetmint.transactions.types.assets.transfer import Transfer from planetmint.web import server @@ -133,7 +136,7 @@ def main(): privkey = 'CfdqtD7sS7FgkMoGPXw55MVGGFwQLAoHYTcBhZDtF99Z' pubkey = '4K9sWUMFwTgaDGPfdynrbxWqWS6sWmKbZoTjxLtVUibD' asset = {'msg': 'Hello Planetmint!'} - tx = Transaction.create([pubkey], [([pubkey], 1)], asset=asset, metadata={'sequence': 0}) + tx = Create.generate([pubkey], [([pubkey], 1)], asset=asset, metadata={'sequence': 0}) tx = tx.sign([privkey]) ctx['tx'] = pretty_json(tx.to_dict()) ctx['public_keys'] = tx.outputs[0].public_keys[0] @@ -147,7 +150,7 @@ def main(): input_ = Input(fulfillment=tx.outputs[cid].fulfillment, fulfills=TransactionLink(txid=tx.id, output=cid), owners_before=tx.outputs[cid].public_keys) - tx_transfer = Transaction.transfer([input_], [([pubkey_transfer], 1)], asset_id=tx.id, metadata={'sequence': 1}) + tx_transfer = Transfer.generate([input_], [([pubkey_transfer], 1)], asset_id=tx.id, metadata={'sequence': 1}) tx_transfer = tx_transfer.sign([privkey]) ctx['tx_transfer'] = pretty_json(tx_transfer.to_dict()) ctx['public_keys_transfer'] = tx_transfer.outputs[0].public_keys[0] @@ -160,7 +163,7 @@ def main(): input_ = Input(fulfillment=tx_transfer.outputs[cid].fulfillment, fulfills=TransactionLink(txid=tx_transfer.id, output=cid), owners_before=tx_transfer.outputs[cid].public_keys) - tx_transfer_last = Transaction.transfer([input_], [([pubkey_transfer_last], 1)], + tx_transfer_last = Transfer.generate([input_], [([pubkey_transfer_last], 1)], asset_id=tx.id, metadata={'sequence': 2}) tx_transfer_last = tx_transfer_last.sign([privkey_transfer]) ctx['tx_transfer_last'] = pretty_json(tx_transfer_last.to_dict()) diff --git a/docs/root/requirements.txt b/docs/root/requirements.txt index 81df20d..8d8ed0f 100644 --- a/docs/root/requirements.txt +++ b/docs/root/requirements.txt @@ -1,9 +1,38 @@ -Sphinx~=1.0 -recommonmark>=0.4.0 -sphinx-rtd-theme>=0.1.9 -sphinxcontrib-napoleon>=0.4.4 -sphinxcontrib-httpdomain>=1.5.0 -pyyaml>=4.2b1 -aafigure>=0.6 -packaging~=18.0 -wget +aafigure==0.6 +alabaster==0.7.12 +Babel==2.10.1 +certifi==2021.10.8 +charset-normalizer==2.0.12 +commonmark==0.9.1 +docutils==0.17.1 +idna +imagesize==1.3.0 +importlib-metadata==4.11.3 +Jinja2==3.0.0 +markdown-it-py==2.1.0 +MarkupSafe==2.1.1 +mdit-py-plugins==0.3.0 +mdurl==0.1.1 +myst-parser==0.17.2 +packaging==21.3 +pockets==0.9.1 +Pygments==2.12.0 +pyparsing==3.0.8 +pytz==2022.1 +PyYAML>=5.4.0 +requests>=2.25i.1 +six==1.16.0 +snowballstemmer==2.2.0 +Sphinx==4.5.0 +sphinx-rtd-theme==1.0.0 +sphinxcontrib-applehelp==1.0.2 +sphinxcontrib-devhelp==1.0.2 +sphinxcontrib-htmlhelp==2.0.0 +sphinxcontrib-httpdomain==1.8.0 +sphinxcontrib-jsmath==1.0.1 +sphinxcontrib-napoleon==0.7 +sphinxcontrib-qthelp==1.0.3 +sphinxcontrib-serializinghtml==1.1.5 +urllib3==1.26.9 +wget==3.2 +zipp==3.8.0 diff --git a/docs/root/source/conf.py b/docs/root/source/conf.py index 8dee073..5c082ea 100644 --- a/docs/root/source/conf.py +++ b/docs/root/source/conf.py @@ -23,9 +23,6 @@ import sys import inspect from os import rename, remove -from recommonmark.parser import CommonMarkParser - - # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the @@ -51,9 +48,12 @@ sys.path.insert(0,parentdir) # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. +project = 'Planetmint' + import sphinx_rtd_theme extensions = [ + 'myst_parser', 'sphinx.ext.autosectionlabel', 'sphinx.ext.autodoc', 'sphinx.ext.intersphinx', @@ -99,10 +99,6 @@ autodoc_default_options = { 'members': None, } -source_parsers = { - '.md': CommonMarkParser, -} - # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # @@ -115,9 +111,8 @@ source_suffix = ['.rst', '.md'] # The master toctree document. master_doc = 'index' - +autosectionlabel_prefix_document = True # General information about the project. -project = 'Planetmint' now = datetime.datetime.now() copyright = str(now.year) + ', Planetmint Contributors' author = 'Planetmint Contributors' @@ -137,7 +132,7 @@ release = _version['__version__'] # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = None +language = 'en' # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: diff --git a/docs/root/source/contributing/dev-setup-coding-and-contribution-process/run-node-as-processes.md b/docs/root/source/contributing/dev-setup-coding-and-contribution-process/run-node-as-processes.md index dea26ce..ba60915 100644 --- a/docs/root/source/contributing/dev-setup-coding-and-contribution-process/run-node-as-processes.md +++ b/docs/root/source/contributing/dev-setup-coding-and-contribution-process/run-node-as-processes.md @@ -30,9 +30,9 @@ The version of Planetmint Server described in these docs only works well with Te ```bash $ sudo apt install -y unzip -$ wget https://github.com/tendermint/tendermint/releases/download/v0.31.5/tendermint_v0.31.5_linux_amd64.zip -$ unzip tendermint_v0.31.5_linux_amd64.zip -$ rm tendermint_v0.31.5_linux_amd64.zip +$ wget https://github.com/tendermint/tendermint/releases/download/v0.34.15/tendermint_v0.34.15_linux_amd64.zip +$ unzip tendermint_v0.34.15_linux_amd64.zip +$ rm tendermint_v0.34.15_linux_amd64.zip $ sudo mv tendermint /usr/local/bin ``` diff --git a/docs/root/source/contributing/index.rst b/docs/root/source/contributing/index.rst index cf0b606..a109838 100644 --- a/docs/root/source/contributing/index.rst +++ b/docs/root/source/contributing/index.rst @@ -11,7 +11,7 @@ There are many ways you can contribute to Planetmint. It includes several sub-projects. - `Planetmint Server `_ -- `Planetmint Python Driver `_ +- `Planetmint Python Driver `_ - `Planetmint JavaScript Driver `_ - `Planetmint Java Driver `_ - `cryptoconditions `_ (a Python package by us) diff --git a/docs/root/source/contributing/ways-to-contribute/write-docs.md b/docs/root/source/contributing/ways-to-contribute/write-docs.md index d223243..23bf7ea 100644 --- a/docs/root/source/contributing/ways-to-contribute/write-docs.md +++ b/docs/root/source/contributing/ways-to-contribute/write-docs.md @@ -19,7 +19,7 @@ If you're writing code, you should also update any related docs. However, you mi You can certainly do that! - The docs for Planetmint Server live under ``planetmint/docs/`` in the ``planetmint/planetmint`` repo. -- There are docs for the Python driver under ``planetmint-driver/docs/`` in the ``planetmint/planetmint-driver`` repo. +- There are docs for the Python driver under ``planetmint-driver/docs/`` in the ``planetmint/planetmint-driver-python`` repo. - There are docs for the JavaScript driver under ``planetmint/js-bigchaindb-driver`` in the ``planetmint/js-bigchaindb-driver`` repo. - The source code for the Planetmint website is in a private repo, but we can give you access if you ask. diff --git a/docs/root/source/installation/api/http-samples/api-index-response.http b/docs/root/source/installation/api/http-samples/api-index-response.http index 7ba7be8..fe767cd 100644 --- a/docs/root/source/installation/api/http-samples/api-index-response.http +++ b/docs/root/source/installation/api/http-samples/api-index-response.http @@ -4,7 +4,7 @@ Content-Type: application/json { "assets": "/assets/", "blocks": "/blocks/", - "docs": "https://docs.planetmint.com/projects/server/en/v0.9.0/http-client-server-api.html", + "docs": "https://docs.planetmint.com/projects/server/en/v0.9.2/http-client-server-api.html", "metadata": "/metadata/", "outputs": "/outputs/", "streams": "ws://localhost:9985/api/v1/streams/valid_transactions", diff --git a/docs/root/source/installation/api/http-samples/index-response.http b/docs/root/source/installation/api/http-samples/index-response.http index c593a91..789da5e 100644 --- a/docs/root/source/installation/api/http-samples/index-response.http +++ b/docs/root/source/installation/api/http-samples/index-response.http @@ -6,7 +6,7 @@ Content-Type: application/json "v1": { "assets": "/api/v1/assets/", "blocks": "/api/v1/blocks/", - "docs": "https://docs.planetmint.com/projects/server/en/v0.9.0/http-client-server-api.html", + "docs": "https://docs.planetmint.com/projects/server/en/v0.9.2/http-client-server-api.html", "metadata": "/api/v1/metadata/", "outputs": "/api/v1/outputs/", "streams": "ws://localhost:9985/api/v1/streams/valid_transactions", @@ -14,7 +14,7 @@ Content-Type: application/json "validators": "/api/v1/validators" } }, - "docs": "https://docs.planetmint.com/projects/server/en/v0.9.0/", + "docs": "https://docs.planetmint.com/projects/server/en/v0.9.2/", "software": "Planetmint", - "version": "0.9.0" + "version": "0.9.2" } diff --git a/docs/root/source/installation/network-setup/k8s-deployment-template/workflow.rst b/docs/root/source/installation/network-setup/k8s-deployment-template/workflow.rst index 4f00dae..1d0148f 100644 --- a/docs/root/source/installation/network-setup/k8s-deployment-template/workflow.rst +++ b/docs/root/source/installation/network-setup/k8s-deployment-template/workflow.rst @@ -60,7 +60,7 @@ you can do this: .. code:: $ mkdir $(pwd)/tmdata - $ docker run --rm -v $(pwd)/tmdata:/tendermint/config tendermint/tendermint:v0.31.5 init + $ docker run --rm -v $(pwd)/tmdata:/tendermint/config tendermint/tendermint:v0.34.15 init $ cat $(pwd)/tmdata/genesis.json You should see something that looks like: diff --git a/docs/root/source/properties.md b/docs/root/source/properties.md index 862f464..861fe0a 100644 --- a/docs/root/source/properties.md +++ b/docs/root/source/properties.md @@ -7,7 +7,7 @@ Code is Apache-2.0 and docs are CC-BY-4.0 # Properties of Planetmint -### Decentralization +## Decentralization Decentralization means that no one owns or controls everything, and there is no single point of failure. @@ -23,12 +23,12 @@ If someone has (or gets) admin access to a node, they can mess with that node (e It’s worth noting that not even the admin or superuser of a node can transfer assets. The only way to create a valid transfer transaction is to fulfill the current crypto-conditions on the asset, and the admin/superuser can’t do that because the admin user doesn’t have the necessary information (e.g. private keys). -### Byzantine Fault Tolerance +## Byzantine Fault Tolerance [Tendermint](https://tendermint.io/) is used for consensus and transaction replication, and Tendermint is [Byzantine Fault Tolerant (BFT)](https://en.wikipedia.org/wiki/Byzantine_fault_tolerance). -### Node Diversity +## Node Diversity Steps should be taken to make it difficult for any one actor or event to control or damage “enough” of the nodes. (Because Planetmint Server uses Tendermint, "enough" is ⅓.) There are many kinds of diversity to consider, listed below. It may be quite difficult to have high diversity of all kinds. @@ -39,7 +39,7 @@ Steps should be taken to make it difficult for any one actor or event to control Note: If all the nodes are running the same code, i.e. the same implementation of Planetmint, then a bug in that code could be used to compromise all of the nodes. Ideally, there would be several different, well-maintained implementations of Planetmint Server (e.g. one in Python, one in Go, etc.), so that a consortium could also have a diversity of server implementations. Similar remarks can be made about the operating system. -### Immutability +## Immutability The blockchain community often describes blockchains as “immutable.” If we interpret that word literally, it means that blockchain data is unchangeable or permanent, which is absurd. The data _can_ be changed. For example, a plague might drive humanity extinct; the data would then get corrupted over time due to water damage, thermal noise, and the general increase of entropy. diff --git a/docs/root/source/terminology.md b/docs/root/source/terminology.md index 6f42b32..6827de7 100644 --- a/docs/root/source/terminology.md +++ b/docs/root/source/terminology.md @@ -9,19 +9,17 @@ Code is Apache-2.0 and docs are CC-BY-4.0 There is some specialized terminology associated with Planetmint. To get started, you should at least know the following: -### Planetmint Node +## Planetmint Node -<<<<<<< HEAD -A **Planetmint node** is a machine (or logical machine) running [Planetmint Server](https://docs.planetmint.com/projects/server/en/latest/introduction.html) and related software. Each node is controlled by one person or organization. -======= -A **Planetmint node** is a machine (or logical machine) running [Planetmint Server](https://docs.planetmint.io/projects/server/en/latest/introduction.html) and related software. Each node is controlled by one person or organization. ->>>>>>> 3bfc3298f8210b135084e823eedd47f213538088 +**Planetmint node** is a machine (or logical machine) running [Planetmint Server](https://docs.planetmint.com/projects/server/en/latest/introduction.html) and related software. Each node is controlled by one person or organization. -### Planetmint Network +**Planetmint node** is a machine (or logical machine) running [Planetmint Server](https://docs.planetmint.io/projects/server/en/latest/introduction.html) and related software. Each node is controlled by one person or organization. + +## Planetmint Network A set of Planetmint nodes can connect to each other to form a **Planetmint network**. Each node in the network runs the same software. A Planetmint network may have additional machines to do things such as monitoring. -### Planetmint Consortium +## Planetmint Consortium The people and organizations that run the nodes in a Planetmint network belong to a **Planetmint consortium** (i.e. another organization). A consortium must have some sort of governance structure to make decisions. If a Planetmint network is run by a single company, then the "consortium" is just that company. @@ -29,7 +27,7 @@ The people and organizations that run the nodes in a Planetmint network belong t A Planetmint network is just a bunch of connected nodes. A consortium is an organization which has a Planetmint network, and where each node in that network has a different operator. -### Transactions +## Transactions Are described in detail in `Planetmint Transactions Spec `_ . @@ -80,10 +78,7 @@ You could do more elaborate things too. As one example, each time someone writes ### Role-Based Access Control (RBAC) -<<<<<<< HEAD In September 2017, we published a [blog post about how one can define an RBAC sub-system on top of Planetmint](https://blog.planetmint.com/role-based-access-control-for-planetmint-assets-b7cada491997). -======= -In September 2017, BigchainDB published a [blog post about how one can define an RBAC sub-system on top of Planetmint](https://blog.bigchaindb.com/role-based-access-control-for-planetmint-assets-b7cada491997). ->>>>>>> 3bfc3298f8210b135084e823eedd47f213538088 + At the time of writing (January 2018), doing so required the use of a plugin, so it's not possible using standard Planetmint (which is what's available on the [IPDB Testnet](https://test.ipdb.io/>). That may change in the future. If you're interested, `contact IPDB `_. diff --git a/integration/README.md b/integration/README.md new file mode 100644 index 0000000..ba1e204 --- /dev/null +++ b/integration/README.md @@ -0,0 +1,23 @@ + + +# Integration test suite +This directory contains the integration test suite for Planetmint. + +The suite uses Docker Compose to spin up multiple Planetmint nodes, run tests with `pytest` as well as cli tests and teardown. + +## Running the tests +Run `make test-integration` in the project root directory. + +By default the integration test suite spins up four planetmint nodes. If you desire to run a different configuration you can pass `SCALE=` as an environmental variable. + +## Writing and documenting the tests +Tests are sometimes difficult to read. For integration tests, we try to be really explicit on what the test is doing, so please write code that is *simple* and easy to understand. We decided to use literate-programming documentation. To generate the documentation for python tests run: + +```bash +make docs-integration +``` diff --git a/integration/cli/chain-migration.sh b/integration/cli/chain-migration.sh new file mode 100755 index 0000000..448ffb1 --- /dev/null +++ b/integration/cli/chain-migration.sh @@ -0,0 +1,47 @@ +#!/bin/bash +# Copyright © 2020 Interplanetary Database Association e.V., +# Planetmint and IPDB software contributors. +# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +# Code is Apache-2.0 and docs are CC-BY-4.0 + +# Add chain migration test +check_status () { + status=$(ssh -o "StrictHostKeyChecking=no" -i \~/.ssh/id_rsa root@$1 'bash -s' < scripts/election.sh show_election $2 | tail -n 1) + status=${status#*=} + if [ $status != $3 ]; then + exit 1 + fi +} + +# Read host names from shared +readarray -t HOSTNAMES < /shared/hostnames + +# Split into proposer and approvers +PROPOSER=${HOSTNAMES[0]} +APPROVERS=${HOSTNAMES[@]:1} + +# Propose chain migration +result=$(ssh -o "StrictHostKeyChecking=no" -i \~/.ssh/id_rsa root@${PROPOSER} 'bash -s' < scripts/election.sh migrate) + +# Check if election is ongoing and approve chain migration +for APPROVER in ${APPROVERS[@]}; do + # Check if election is still ongoing + check_status ${APPROVER} $result ongoing + ssh -o "StrictHostKeyChecking=no" -i ~/.ssh/id_rsa root@${APPROVER} 'bash -s' < scripts/election.sh approve $result +done + +# Status of election should be concluded +status=$(ssh -o "StrictHostKeyChecking=no" -i \~/.ssh/id_rsa root@${PROPOSER} 'bash -s' < scripts/election.sh show_election $result) +status=${status#*INFO:planetmint.commands.planetmint:} +status=("$status[@]") + + +# TODO: Get status, chain_id, app_hash and validators to restore planetmint on all nodes +# References: +# https://github.com/bigchaindb/BEPs/tree/master/42 +# http://docs.bigchaindb.com/en/latest/installation/node-setup/bigchaindb-cli.html +for word in $status; do + echo $word +done + +echo ${status#*validators=} \ No newline at end of file diff --git a/integration/cli/upsert-new-validator.sh b/integration/cli/upsert-new-validator.sh new file mode 100755 index 0000000..4f63568 --- /dev/null +++ b/integration/cli/upsert-new-validator.sh @@ -0,0 +1,33 @@ +#!/bin/bash +# Copyright © 2020 Interplanetary Database Association e.V., +# Planetmint and IPDB software contributors. +# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +# Code is Apache-2.0 and docs are CC-BY-4.0 + +check_status () { + status=$(ssh -o "StrictHostKeyChecking=no" -i \~/.ssh/id_rsa root@$1 'bash -s' < scripts/election.sh show_election $2 | tail -n 1) + status=${status#*=} + if [ $status != $3 ]; then + exit 1 + fi +} + +# Read host names from shared +readarray -t HOSTNAMES < /shared/hostnames + +# Split into proposer and approvers +PROPOSER=${HOSTNAMES[0]} +APPROVERS=${HOSTNAMES[@]:1} + +# Propose validator upsert +result=$(ssh -o "StrictHostKeyChecking=no" -i \~/.ssh/id_rsa root@${PROPOSER} 'bash -s' < scripts/election.sh elect 2) + +# Check if election is ongoing and approve validator upsert +for APPROVER in ${APPROVERS[@]}; do + # Check if election is still ongoing + check_status ${APPROVER} $result ongoing + ssh -o "StrictHostKeyChecking=no" -i ~/.ssh/id_rsa root@${APPROVER} 'bash -s' < scripts/election.sh approve $result +done + +# Status of election should be concluded +check_status ${PROPOSER} $result concluded \ No newline at end of file diff --git a/integration/python/.gitignore b/integration/python/.gitignore new file mode 100644 index 0000000..5c457d7 --- /dev/null +++ b/integration/python/.gitignore @@ -0,0 +1 @@ +docs \ No newline at end of file diff --git a/integration/python/Dockerfile b/integration/python/Dockerfile new file mode 100644 index 0000000..2498a58 --- /dev/null +++ b/integration/python/Dockerfile @@ -0,0 +1,22 @@ +FROM python:3.9 + +RUN apt-get update \ + && pip install -U pip \ + && apt-get autoremove \ + && apt-get clean +RUN apt-get install -y vim +RUN apt-get update +RUN apt-get install -y build-essential cmake openssh-client openssh-server +RUN apt-get install -y zsh + +RUN mkdir -p /src +RUN pip install --upgrade meson ninja +RUN pip install --upgrade \ + pytest~=6.2.5 \ + planetmint-driver~=0.9.0 \ + pycco \ + websocket-client~=0.47.0 \ + #git+https://github.com/planetmint/cryptoconditions.git@gitzenroom \ + #git+https://github.com/planetmint/planetmint-driver.git@gitzenroom \ + blns + diff --git a/planetmint/common/__init__.py b/integration/python/src/__init__.py similarity index 100% rename from planetmint/common/__init__.py rename to integration/python/src/__init__.py diff --git a/integration/python/src/conftest.py b/integration/python/src/conftest.py new file mode 100644 index 0000000..808914b --- /dev/null +++ b/integration/python/src/conftest.py @@ -0,0 +1,95 @@ +# Copyright © 2020 Interplanetary Database Association e.V., +# Planetmint and IPDB software contributors. +# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +# Code is Apache-2.0 and docs are CC-BY-4.0 + +import pytest + +GENERATE_KEYPAIR = \ + """Rule input encoding base58 + Rule output encoding base58 + Scenario 'ecdh': Create the keypair + Given that I am known as 'Pippo' + When I create the ecdh key + When I create the testnet key + Then print data""" + +# secret key to public key +SK_TO_PK = \ + """Rule input encoding base58 + Rule output encoding base58 + Scenario 'ecdh': Create the keypair + Given that I am known as '{}' + Given I have the 'keys' + When I create the ecdh public key + When I create the testnet address + Then print my 'ecdh public key' + Then print my 'testnet address'""" + +FULFILL_SCRIPT = \ + """Rule input encoding base58 + Rule output encoding base58 + Scenario 'ecdh': Bob verifies the signature from Alice + Given I have a 'ecdh public key' from 'Alice' + Given that I have a 'string dictionary' named 'houses' inside 'asset' + Given I have a 'signature' named 'data.signature' inside 'result' + When I verify the 'houses' has a signature in 'data.signature' by 'Alice' + Then print the string 'ok'""" + +HOUSE_ASSETS = { + "data": { + "houses": [ + { + "name": "Harry", + "team": "Gryffindor", + }, + { + "name": "Draco", + "team": "Slytherin", + } + ], + } +} + +ZENROOM_DATA = { + 'also': 'more data' +} + +CONDITION_SCRIPT = """Rule input encoding base58 + Rule output encoding base58 + Scenario 'ecdh': create the signature of an object + Given I have the 'keys' + Given that I have a 'string dictionary' named 'houses' inside 'asset' + When I create the signature of 'houses' + When I rename the 'signature' to 'data.signature' + Then print the 'data.signature'""" + + +@pytest.fixture +def gen_key_zencode(): + return GENERATE_KEYPAIR + + +@pytest.fixture +def secret_key_to_private_key_zencode(): + return SK_TO_PK + + +@pytest.fixture +def fulfill_script_zencode(): + return FULFILL_SCRIPT + + +@pytest.fixture +def condition_script_zencode(): + return CONDITION_SCRIPT + + +@pytest.fixture +def zenroom_house_assets(): + return HOUSE_ASSETS + + +@pytest.fixture +def zenroom_data(): + return ZENROOM_DATA diff --git a/planetmint/elections/__init__.py b/integration/python/src/helper/__init__.py similarity index 100% rename from planetmint/elections/__init__.py rename to integration/python/src/helper/__init__.py diff --git a/integration/python/src/helper/hosts.py b/integration/python/src/helper/hosts.py new file mode 100644 index 0000000..b14f875 --- /dev/null +++ b/integration/python/src/helper/hosts.py @@ -0,0 +1,36 @@ +# Copyright © 2020 Interplanetary Database Association e.V., +# Planetmint and IPDB software contributors. +# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +# Code is Apache-2.0 and docs are CC-BY-4.0 + +from typing import List + +from planetmint_driver import Planetmint + + +class Hosts: + hostnames = [] + connections = [] + + def __init__(self, filepath): + self.set_hostnames(filepath=filepath) + self.set_connections() + + def set_hostnames(self, filepath) -> None: + with open(filepath) as f: + self.hostnames = f.readlines() + + def set_connections(self) -> None: + self.connections = list(map(lambda h: Planetmint(h), self.hostnames)) + + def get_connection(self, index=0) -> Planetmint: + return self.connections[index] + + def get_transactions(self, tx_id) -> List: + return list(map(lambda connection: connection.transactions.retrieve(tx_id), self.connections)) + + def assert_transaction(self, tx_id) -> None: + txs = self.get_transactions(tx_id) + for tx in txs: + assert txs[0] == tx, \ + 'Cannot find transaction {}'.format(tx_id) diff --git a/integration/python/src/test_basic.py b/integration/python/src/test_basic.py new file mode 100644 index 0000000..691dbc3 --- /dev/null +++ b/integration/python/src/test_basic.py @@ -0,0 +1,83 @@ +# Copyright © 2020 Interplanetary Database Association e.V., +# Planetmint and IPDB software contributors. +# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +# Code is Apache-2.0 and docs are CC-BY-4.0 + +# import Planetmint and create object +from planetmint_driver.crypto import generate_keypair + +# import helper to manage multiple nodes +from .helper.hosts import Hosts + +import time + + +def test_basic(): + # Setup up connection to Planetmint integration test nodes + hosts = Hosts('/shared/hostnames') + pm_alpha = hosts.get_connection() + + # genarate a keypair + alice = generate_keypair() + + # create a digital asset for Alice + game_boy_token = { + 'data': { + 'hash': '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', + 'storageID': '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', + }, + } + + # prepare the transaction with the digital asset and issue 10 tokens to bob + prepared_creation_tx = pm_alpha.transactions.prepare( + operation='CREATE', + metadata={ + 'hash': '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', + 'storageID': '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', }, + signers=alice.public_key, + recipients=[([alice.public_key], 10)], + asset=game_boy_token) + + # fulfill and send the transaction + fulfilled_creation_tx = pm_alpha.transactions.fulfill( + prepared_creation_tx, + private_keys=alice.private_key) + pm_alpha.transactions.send_commit(fulfilled_creation_tx) + time.sleep(1) + + creation_tx_id = fulfilled_creation_tx['id'] + + # Assert that transaction is stored on all planetmint nodes + hosts.assert_transaction(creation_tx_id) + + # Transfer + # create the output and inout for the transaction + transfer_asset = {'id': creation_tx_id} + output_index = 0 + output = fulfilled_creation_tx['outputs'][output_index] + transfer_input = {'fulfillment': output['condition']['details'], + 'fulfills': {'output_index': output_index, + 'transaction_id': transfer_asset['id']}, + 'owners_before': output['public_keys']} + + # prepare the transaction and use 3 tokens + prepared_transfer_tx = pm_alpha.transactions.prepare( + operation='TRANSFER', + asset=transfer_asset, + inputs=transfer_input, + metadata={'hash': '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', + 'storageID': '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', }, + recipients=[([alice.public_key], 10)]) + + # fulfill and send the transaction + fulfilled_transfer_tx = pm_alpha.transactions.fulfill( + prepared_transfer_tx, + private_keys=alice.private_key) + sent_transfer_tx = pm_alpha.transactions.send_commit(fulfilled_transfer_tx) + + time.sleep(1) + + transfer_tx_id = sent_transfer_tx['id'] + + # Assert that transaction is stored on both planetmint nodes + hosts.assert_transaction(transfer_tx_id) diff --git a/integration/python/src/test_divisible_asset.py b/integration/python/src/test_divisible_asset.py new file mode 100644 index 0000000..c324f61 --- /dev/null +++ b/integration/python/src/test_divisible_asset.py @@ -0,0 +1,183 @@ +# Copyright © 2020 Interplanetary Database Association e.V., +# Planetmint and IPDB software contributors. +# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +# Code is Apache-2.0 and docs are CC-BY-4.0 + +# # Divisible assets integration testing +# This test checks if we can successfully divide assets. +# The script tests various things like: +# +# - create a transaction with a divisible asset and issue them to someone +# - check if the transaction is stored and has the right amount of tokens +# - spend some tokens +# - try to spend more tokens than available +# +# We run a series of checks for each step, that is retrieving +# the transaction from the remote system, and also checking the `amount` +# of a given transaction. +# +# This integration test is a rip-off of our +# [tutorial](https://docs.planetmint.com/projects/py-driver/en/latest/usage.html). + +# ## Imports +# We need the `pytest` package to catch the `BadRequest` exception properly. +# And of course, we also need the `BadRequest`. +import pytest +from planetmint_driver.exceptions import BadRequest + +# Import generate_keypair to create actors +from planetmint_driver.crypto import generate_keypair + +# import helper to manage multiple nodes +from .helper.hosts import Hosts + + +def test_divisible_assets(): + # ## Set up a connection to Planetmint + # Check [test_basic.py](./test_basic.html) to get some more details + # about the endpoint. + hosts = Hosts('/shared/hostnames') + pm = hosts.get_connection() + + # Oh look, it is Alice again and she brought her friend Bob along. + alice, bob = generate_keypair(), generate_keypair() + + # ## Alice creates a time sharing token + # Alice wants to go on vacation, while Bobs bike just broke down. + # Alice decides to rent her bike to Bob while she is gone. + # So she prepares a `CREATE` transaction to issues 10 tokens. + # First, she prepares an asset for a time sharing token. As you can see in + # the description, Bob and Alice agree that each token can be used to ride + # the bike for one hour. + + bike_token = { + 'data': { + 'token_for': { + 'bike': { + 'serial_number': 420420 + } + }, + 'description': 'Time share token. Each token equals one hour of riding.', + }, + } + + # She prepares a `CREATE` transaction and issues 10 tokens. + # Here, Alice defines in a tuple that she wants to assign + # these 10 tokens to Bob. + prepared_token_tx = pm.transactions.prepare( + operation='CREATE', + signers=alice.public_key, + recipients=[([bob.public_key], 10)], + asset=bike_token) + + # She fulfills and sends the transaction. + fulfilled_token_tx = pm.transactions.fulfill( + prepared_token_tx, + private_keys=alice.private_key) + + pm.transactions.send_commit(fulfilled_token_tx) + + # We store the `id` of the transaction to use it later on. + bike_token_id = fulfilled_token_tx['id'] + + # Let's check if the transaction was successful. + assert pm.transactions.retrieve(bike_token_id), \ + 'Cannot find transaction {}'.format(bike_token_id) + + # Bob owns 10 tokens now. + assert pm.transactions.retrieve(bike_token_id)['outputs'][0][ + 'amount'] == '10' + + # ## Bob wants to use the bike + # Now that Bob got the tokens and the sun is shining, he wants to get out + # with the bike for three hours. + # To use the bike he has to send the tokens back to Alice. + # To learn about the details of transferring a transaction check out + # [test_basic.py](./test_basic.html) + transfer_asset = {'id': bike_token_id} + + output_index = 0 + output = fulfilled_token_tx['outputs'][output_index] + transfer_input = {'fulfillment': output['condition']['details'], + 'fulfills': {'output_index': output_index, + 'transaction_id': fulfilled_token_tx[ + 'id']}, + 'owners_before': output['public_keys']} + + # To use the tokens Bob has to reassign 7 tokens to himself and the + # amount he wants to use to Alice. + prepared_transfer_tx = pm.transactions.prepare( + operation='TRANSFER', + asset=transfer_asset, + inputs=transfer_input, + recipients=[([alice.public_key], 3), ([bob.public_key], 7)]) + + # He signs and sends the transaction. + fulfilled_transfer_tx = pm.transactions.fulfill( + prepared_transfer_tx, + private_keys=bob.private_key) + + sent_transfer_tx = pm.transactions.send_commit(fulfilled_transfer_tx) + + # First, Bob checks if the transaction was successful. + assert pm.transactions.retrieve( + fulfilled_transfer_tx['id']) == sent_transfer_tx + + hosts.assert_transaction(fulfilled_transfer_tx['id']) + # There are two outputs in the transaction now. + # The first output shows that Alice got back 3 tokens... + assert pm.transactions.retrieve( + fulfilled_transfer_tx['id'])['outputs'][0]['amount'] == '3' + + # ... while Bob still has 7 left. + assert pm.transactions.retrieve( + fulfilled_transfer_tx['id'])['outputs'][1]['amount'] == '7' + + # ## Bob wants to ride the bike again + # It's been a week and Bob wants to right the bike again. + # Now he wants to ride for 8 hours, that's a lot Bob! + # He prepares the transaction again. + + transfer_asset = {'id': bike_token_id} + # This time we need an `output_index` of 1, since we have two outputs + # in the `fulfilled_transfer_tx` we created before. The first output with + # index 0 is for Alice and the second output is for Bob. + # Since Bob wants to spend more of his tokens he has to provide the + # correct output with the correct amount of tokens. + output_index = 1 + + output = fulfilled_transfer_tx['outputs'][output_index] + + transfer_input = {'fulfillment': output['condition']['details'], + 'fulfills': {'output_index': output_index, + 'transaction_id': fulfilled_transfer_tx['id']}, + 'owners_before': output['public_keys']} + + # This time Bob only provides Alice in the `recipients` because he wants + # to spend all his tokens + prepared_transfer_tx = pm.transactions.prepare( + operation='TRANSFER', + asset=transfer_asset, + inputs=transfer_input, + recipients=[([alice.public_key], 8)]) + + fulfilled_transfer_tx = pm.transactions.fulfill( + prepared_transfer_tx, + private_keys=bob.private_key) + + # Oh Bob, what have you done?! You tried to spend more tokens than you had. + # Remember Bob, last time you spent 3 tokens already, + # so you only have 7 left. + with pytest.raises(BadRequest) as error: + pm.transactions.send_commit(fulfilled_transfer_tx) + + # Now Bob gets an error saying that the amount he wanted to spent is + # higher than the amount of tokens he has left. + assert error.value.args[0] == 400 + message = 'Invalid transaction (AmountError): The amount used in the ' \ + 'inputs `7` needs to be same as the amount used in the ' \ + 'outputs `8`' + assert error.value.args[2]['message'] == message + + # We have to stop this test now, I am sorry, but Bob is pretty upset + # about his mistake. See you next time :) diff --git a/integration/python/src/test_double_spend.py b/integration/python/src/test_double_spend.py new file mode 100644 index 0000000..1a17738 --- /dev/null +++ b/integration/python/src/test_double_spend.py @@ -0,0 +1,48 @@ +# Copyright © 2020 Interplanetary Database Association e.V., +# Planetmint and IPDB software contributors. +# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +# Code is Apache-2.0 and docs are CC-BY-4.0 + +# # Double Spend testing +# This test challenge the system with double spends. +from uuid import uuid4 +from threading import Thread +import queue + +import planetmint_driver.exceptions +from planetmint_driver.crypto import generate_keypair + +from .helper.hosts import Hosts + + +def test_double_create(): + hosts = Hosts('/shared/hostnames') + pm = hosts.get_connection() + alice = generate_keypair() + + results = queue.Queue() + + tx = pm.transactions.fulfill( + pm.transactions.prepare( + operation='CREATE', + signers=alice.public_key, + asset={'data': {'uuid': str(uuid4())}}), + private_keys=alice.private_key) + + def send_and_queue(tx): + try: + pm.transactions.send_commit(tx) + results.put('OK') + except planetmint_driver.exceptions.TransportError: + results.put('FAIL') + + t1 = Thread(target=send_and_queue, args=(tx, )) + t2 = Thread(target=send_and_queue, args=(tx, )) + + t1.start() + t2.start() + + results = [results.get(timeout=2), results.get(timeout=2)] + + assert results.count('OK') == 1 + assert results.count('FAIL') == 1 diff --git a/integration/python/src/test_multiple_owners.py b/integration/python/src/test_multiple_owners.py new file mode 100644 index 0000000..98f3ea6 --- /dev/null +++ b/integration/python/src/test_multiple_owners.py @@ -0,0 +1,133 @@ +# Copyright © 2020 Interplanetary Database Association e.V., +# Planetmint and IPDB software contributors. +# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +# Code is Apache-2.0 and docs are CC-BY-4.0 + +# # Multisignature integration testing +# This test checks if we can successfully create and transfer a transaction +# with multiple owners. +# The script tests various things like: +# +# - create a transaction with multiple owners +# - check if the transaction is stored and has the right amount of public keys +# - transfer the transaction to a third person +# +# We run a series of checks for each step, that is retrieving +# the transaction from the remote system, and also checking the public keys +# of a given transaction. +# +# This integration test is a rip-off of our mutliple signature acceptance tests. + +# # Imports +import time + +# For this test case we need import and use the Python driver +from planetmint_driver.crypto import generate_keypair + +# Import helper to deal with multiple nodes +from .helper.hosts import Hosts + + +def test_multiple_owners(): + # Setup up connection to Planetmint integration test nodes + hosts = Hosts('/shared/hostnames') + pm_alpha = hosts.get_connection() + + # Generate Keypairs for Alice and Bob! + alice, bob = generate_keypair(), generate_keypair() + + # ## Alice and Bob create a transaction + # Alice and Bob just moved into a shared flat, no one can afford these + # high rents anymore. Bob suggests to get a dish washer for the + # kitchen. Alice agrees and here they go, creating the asset for their + # dish washer. + dw_asset = { + 'data': { + 'dish washer': { + 'serial_number': 1337 + } + } + } + + # They prepare a `CREATE` transaction. To have multiple owners, both + # Bob and Alice need to be the recipients. + prepared_dw_tx = pm_alpha.transactions.prepare( + operation='CREATE', + signers=alice.public_key, + recipients=(alice.public_key, bob.public_key), + asset=dw_asset) + + # Now they both sign the transaction by providing their private keys. + # And send it afterwards. + fulfilled_dw_tx = pm_alpha.transactions.fulfill( + prepared_dw_tx, + private_keys=[alice.private_key, bob.private_key]) + + pm_alpha.transactions.send_commit(fulfilled_dw_tx) + + # We store the `id` of the transaction to use it later on. + dw_id = fulfilled_dw_tx['id'] + + time.sleep(1) + + # Use hosts to assert that the transaction is properly propagated to every node + hosts.assert_transaction(dw_id) + + # Let's check if the transaction was successful. + assert pm_alpha.transactions.retrieve(dw_id), \ + 'Cannot find transaction {}'.format(dw_id) + + # The transaction should have two public keys in the outputs. + assert len( + pm_alpha.transactions.retrieve(dw_id)['outputs'][0]['public_keys']) == 2 + + # ## Alice and Bob transfer a transaction to Carol. + # Alice and Bob save a lot of money living together. They often go out + # for dinner and don't cook at home. But now they don't have any dishes to + # wash, so they decide to sell the dish washer to their friend Carol. + + # Hey Carol, nice to meet you! + carol = generate_keypair() + + # Alice and Bob prepare the transaction to transfer the dish washer to + # Carol. + transfer_asset = {'id': dw_id} + + output_index = 0 + output = fulfilled_dw_tx['outputs'][output_index] + transfer_input = {'fulfillment': output['condition']['details'], + 'fulfills': {'output_index': output_index, + 'transaction_id': fulfilled_dw_tx[ + 'id']}, + 'owners_before': output['public_keys']} + + # Now they create the transaction... + prepared_transfer_tx = pm_alpha.transactions.prepare( + operation='TRANSFER', + asset=transfer_asset, + inputs=transfer_input, + recipients=carol.public_key) + + # ... and sign it with their private keys, then send it. + fulfilled_transfer_tx = pm_alpha.transactions.fulfill( + prepared_transfer_tx, + private_keys=[alice.private_key, bob.private_key]) + + sent_transfer_tx = pm_alpha.transactions.send_commit(fulfilled_transfer_tx) + time.sleep(1) + + # Now compare if both nodes returned the same transaction + hosts.assert_transaction(fulfilled_transfer_tx['id']) + + # They check if the transaction was successful. + assert pm_alpha.transactions.retrieve( + fulfilled_transfer_tx['id']) == sent_transfer_tx + + # The owners before should include both Alice and Bob. + assert len( + pm_alpha.transactions.retrieve(fulfilled_transfer_tx['id'])['inputs'][0][ + 'owners_before']) == 2 + + # While the new owner is Carol. + assert pm_alpha.transactions.retrieve(fulfilled_transfer_tx['id'])[ + 'outputs'][0]['public_keys'][0] == carol.public_key diff --git a/integration/python/src/test_naughty_strings.py b/integration/python/src/test_naughty_strings.py new file mode 100644 index 0000000..4a090c0 --- /dev/null +++ b/integration/python/src/test_naughty_strings.py @@ -0,0 +1,100 @@ +# Copyright © 2020 Interplanetary Database Association e.V., +# Planetmint and IPDB software contributors. +# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +# Code is Apache-2.0 and docs are CC-BY-4.0 + +# ## Testing potentially hazardous strings +# This test uses a library of `naughty` strings (code injections, weird unicode chars., etc.) as both keys and values. +# We look for either a successful tx, or in the case that we use a naughty string as a key, and it violates some key +# constraints, we expect to receive a well formatted error message. + +# ## Imports +# Since the naughty strings get encoded and decoded in odd ways, +# we'll use a regex to sweep those details under the rug. +import re + +# We'll use a nice library of naughty strings... +from blns import blns + +# And parameterize our test so each one is treated as a separate test case +import pytest + +# For this test case we import and use the Python Driver. +from planetmint_driver.crypto import generate_keypair +from planetmint_driver.exceptions import BadRequest + +# import helper to manage multiple nodes +from .helper.hosts import Hosts + +naughty_strings = blns.all() + + +# This is our base test case, but we'll reuse it to send naughty strings as both keys and values. +def send_naughty_tx(asset, metadata): + # ## Set up a connection to Planetmint + # Check [test_basic.py](./test_basic.html) to get some more details + # about the endpoint. + hosts = Hosts('/shared/hostnames') + pm = hosts.get_connection() + + # Here's Alice. + alice = generate_keypair() + + # Alice is in a naughty mood today, so she creates a tx with some naughty strings + prepared_transaction = pm.transactions.prepare( + operation='CREATE', + signers=alice.public_key, + asset=asset, + metadata=metadata) + + # She fulfills the transaction + fulfilled_transaction = pm.transactions.fulfill( + prepared_transaction, + private_keys=alice.private_key) + + # The fulfilled tx gets sent to the pm network + try: + sent_transaction = pm.transactions.send_commit(fulfilled_transaction) + except BadRequest as e: + sent_transaction = e + + # If her key contained a '.', began with a '$', or contained a NUL character + regex = r'.*\..*|\$.*|.*\x00.*' + key = next(iter(metadata)) + if re.match(regex, key): + # Then she expects a nicely formatted error code + status_code = sent_transaction.status_code + error = sent_transaction.error + regex = ( + r'\{\s*\n*' + r'\s*"message":\s*"Invalid transaction \(ValidationError\):\s*' + r'Invalid key name.*The key name cannot contain characters.*\n*' + r'\s*"status":\s*400\n*' + r'\s*\}\n*') + assert status_code == 400 + assert re.fullmatch(regex, error), sent_transaction + # Otherwise, she expects to see her transaction in the database + elif 'id' in sent_transaction.keys(): + tx_id = sent_transaction['id'] + assert pm.transactions.retrieve(tx_id) + # If neither condition was true, then something weird happened... + else: + raise TypeError(sent_transaction) + + +@pytest.mark.parametrize("naughty_string", naughty_strings, ids=naughty_strings) +def test_naughty_keys(naughty_string): + + asset = {'data': {naughty_string: 'nice_value'}} + metadata = {naughty_string: 'nice_value'} + + send_naughty_tx(asset, metadata) + + +@pytest.mark.parametrize("naughty_string", naughty_strings, ids=naughty_strings) +def test_naughty_values(naughty_string): + + asset = {'data': {'nice_key': naughty_string}} + metadata = {'nice_key': naughty_string} + + send_naughty_tx(asset, metadata) diff --git a/integration/python/src/test_stream.py b/integration/python/src/test_stream.py new file mode 100644 index 0000000..c93d5c6 --- /dev/null +++ b/integration/python/src/test_stream.py @@ -0,0 +1,131 @@ +# Copyright © 2020 Interplanetary Database Association e.V., +# Planetmint and IPDB software contributors. +# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +# Code is Apache-2.0 and docs are CC-BY-4.0 + +# # Stream Acceptance Test +# This test checks if the event stream works correctly. The basic idea of this +# test is to generate some random **valid** transaction, send them to a +# Planetmint node, and expect those transactions to be returned by the valid +# transactions Stream API. During this test, two threads work together, +# sharing a queue to exchange events. +# +# - The *main thread* first creates and sends the transactions to Planetmint; +# then it run through all events in the shared queue to check if all +# transactions sent have been validated by Planetmint. +# - The *listen thread* listens to the events coming from Planetmint and puts +# them in a queue shared with the main thread. +import queue +import json +from threading import Thread, Event +from uuid import uuid4 + +# For this script, we need to set up a websocket connection, that's the reason +# we import the +# [websocket](https://github.com/websocket-client/websocket-client) module +from websocket import create_connection + +from planetmint_driver.crypto import generate_keypair + +# import helper to manage multiple nodes +from .helper.hosts import Hosts + + +def test_stream(): + # ## Set up the test + # We use the env variable `BICHAINDB_ENDPOINT` to know where to connect. + # Check [test_basic.py](./test_basic.html) for more information. + hosts = Hosts('/shared/hostnames') + pm = hosts.get_connection() + + # *That's pretty bad, but let's do like this for now.* + WS_ENDPOINT = 'ws://{}:9985/api/v1/streams/valid_transactions'.format(hosts.hostnames[0]) + + # Hello to Alice again, she is pretty active in those tests, good job + # Alice! + alice = generate_keypair() + + # We need few variables to keep the state, specifically we need `sent` to + # keep track of all transactions Alice sent to Planetmint, while `received` + # are the transactions Planetmint validated and sent back to her. + sent = [] + received = queue.Queue() + + # In this test we use a websocket. The websocket must be started **before** + # sending transactions to Planetmint, otherwise we might lose some + # transactions. The `ws_ready` event is used to synchronize the main thread + # with the listen thread. + ws_ready = Event() + + # ## Listening to events + # This is the function run by the complementary thread. + def listen(): + # First we connect to the remote endpoint using the WebSocket protocol. + ws = create_connection(WS_ENDPOINT) + + # After the connection has been set up, we can signal the main thread + # to proceed (continue reading, it should make sense in a second.) + ws_ready.set() + + # It's time to consume all events coming from the Planetmint stream API. + # Every time a new event is received, it is put in the queue shared + # with the main thread. + while True: + result = ws.recv() + received.put(result) + + # Put `listen` in a thread, and start it. Note that `listen` is a local + # function and it can access all variables in the enclosing function. + t = Thread(target=listen, daemon=True) + t.start() + + # ## Pushing the transactions to Planetmint + # After starting the listen thread, we wait for it to connect, and then we + # proceed. + ws_ready.wait() + + # Here we prepare, sign, and send ten different `CREATE` transactions. To + # make sure each transaction is different from the other, we generate a + # random `uuid`. + for _ in range(10): + tx = pm.transactions.fulfill( + pm.transactions.prepare( + operation='CREATE', + signers=alice.public_key, + asset={'data': {'uuid': str(uuid4())}}), + private_keys=alice.private_key) + # We don't want to wait for each transaction to be in a block. By using + # `async` mode, we make sure that the driver returns as soon as the + # transaction is pushed to the Planetmint API. Remember: we expect all + # transactions to be in the shared queue: this is a two phase test, + # first we send a bunch of transactions, then we check if they are + # valid (and, in this case, they should). + pm.transactions.send_async(tx) + + # The `id` of every sent transaction is then stored in a list. + sent.append(tx['id']) + + # ## Check the valid transactions coming from Planetmint + # Now we are ready to check if Planetmint did its job. A simple way to + # check if all sent transactions have been processed is to **remove** from + # `sent` the transactions we get from the *listen thread*. At one point in + # time, `sent` should be empty, and we exit the test. + while sent: + # To avoid waiting forever, we have an arbitrary timeout of 5 + # seconds: it should be enough time for Planetmint to create + # blocks, in fact a new block is created every second. If we hit + # the timeout, then game over ¯\\\_(ツ)\_/¯ + try: + event = received.get(timeout=5) + txid = json.loads(event)['transaction_id'] + except queue.Empty: + assert False, 'Did not receive all expected transactions' + + # Last thing is to try to remove the `txid` from the set of sent + # transactions. If this test is running in parallel with others, we + # might get a transaction id of another test, and `remove` can fail. + # It's OK if this happens. + try: + sent.remove(txid) + except ValueError: + pass diff --git a/integration/python/src/test_threshold.py b/integration/python/src/test_threshold.py new file mode 100644 index 0000000..f118651 --- /dev/null +++ b/integration/python/src/test_threshold.py @@ -0,0 +1,336 @@ +# Copyright © 2020 Interplanetary Database Association e.V., +# Planetmint and IPDB software contributors. +# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +# Code is Apache-2.0 and docs are CC-BY-4.0 + +# ## Imports +import time +import json + +# For this test case we need the planetmint_driver.crypto package +import base58 +import sha3 +from cryptoconditions import Ed25519Sha256, ThresholdSha256 +from planetmint_driver.crypto import generate_keypair + +# Import helper to deal with multiple nodes +from .helper.hosts import Hosts + + +def prepare_condition_details(condition: ThresholdSha256): + condition_details = { + 'subconditions': [], + 'threshold': condition.threshold, + 'type': condition.TYPE_NAME + } + + for s in condition.subconditions: + if (s['type'] == 'fulfillment' and s['body'].TYPE_NAME == 'ed25519-sha-256'): + condition_details['subconditions'].append({ + 'type': s['body'].TYPE_NAME, + 'public_key': base58.b58encode(s['body'].public_key).decode() + }) + else: + condition_details['subconditions'].append(prepare_condition_details(s['body'])) + + return condition_details + + +def test_threshold(): + # Setup connection to test nodes + hosts = Hosts('/shared/hostnames') + pm = hosts.get_connection() + + # Generate Keypars for Alice, Bob an Carol! + alice, bob, carol = generate_keypair(), generate_keypair(), generate_keypair() + + # ## Alice and Bob create a transaction + # Alice and Bob just moved into a shared flat, no one can afford these + # high rents anymore. Bob suggests to get a dish washer for the + # kitchen. Alice agrees and here they go, creating the asset for their + # dish washer. + dw_asset = { + 'data': { + 'dish washer': { + 'serial_number': 1337 + } + } + } + + # Create subfulfillments + alice_ed25519 = Ed25519Sha256(public_key=base58.b58decode(alice.public_key)) + bob_ed25519 = Ed25519Sha256(public_key=base58.b58decode(bob.public_key)) + carol_ed25519 = Ed25519Sha256(public_key=base58.b58decode(carol.public_key)) + + # Create threshold condition (2/3) and add subfulfillments + threshold_sha256 = ThresholdSha256(2) + threshold_sha256.add_subfulfillment(alice_ed25519) + threshold_sha256.add_subfulfillment(bob_ed25519) + threshold_sha256.add_subfulfillment(carol_ed25519) + + # Create a condition uri and details for the output object + condition_uri = threshold_sha256.condition.serialize_uri() + condition_details = prepare_condition_details(threshold_sha256) + + # Assemble output and input for the handcrafted tx + output = { + 'amount': '1', + 'condition': { + 'details': condition_details, + 'uri': condition_uri, + }, + 'public_keys': (alice.public_key, bob.public_key, carol.public_key), + } + + # The yet to be fulfilled input: + input_ = { + 'fulfillment': None, + 'fulfills': None, + 'owners_before': (alice.public_key, bob.public_key), + } + + # Assemble the handcrafted transaction + handcrafted_dw_tx = { + 'operation': 'CREATE', + 'asset': dw_asset, + 'metadata': None, + 'outputs': (output,), + 'inputs': (input_,), + 'version': '2.0', + 'id': None, + } + + # Create sha3-256 of message to sign + message = json.dumps( + handcrafted_dw_tx, + sort_keys=True, + separators=(',', ':'), + ensure_ascii=False, + ) + message = sha3.sha3_256(message.encode()) + + # Sign message with Alice's und Bob's private key + alice_ed25519.sign(message.digest(), base58.b58decode(alice.private_key)) + bob_ed25519.sign(message.digest(), base58.b58decode(bob.private_key)) + + # Create fulfillment and add uri to inputs + fulfillment_threshold = ThresholdSha256(2) + fulfillment_threshold.add_subfulfillment(alice_ed25519) + fulfillment_threshold.add_subfulfillment(bob_ed25519) + fulfillment_threshold.add_subcondition(carol_ed25519.condition) + + fulfillment_uri = fulfillment_threshold.serialize_uri() + + handcrafted_dw_tx['inputs'][0]['fulfillment'] = fulfillment_uri + + # Create tx_id for handcrafted_dw_tx and send tx commit + json_str_tx = json.dumps( + handcrafted_dw_tx, + sort_keys=True, + separators=(',', ':'), + ensure_ascii=False, + ) + + dw_creation_txid = sha3.sha3_256(json_str_tx.encode()).hexdigest() + + handcrafted_dw_tx['id'] = dw_creation_txid + + pm.transactions.send_commit(handcrafted_dw_tx) + + time.sleep(1) + + # Assert that the tx is propagated to all nodes + hosts.assert_transaction(dw_creation_txid) + + +def test_weighted_threshold(): + hosts = Hosts('/shared/hostnames') + pm = hosts.get_connection() + + alice, bob, carol = generate_keypair(), generate_keypair(), generate_keypair() + + asset = { + 'data': { + 'trashcan': { + 'animals': ['racoon_1', 'racoon_2'] + } + } + } + + alice_ed25519 = Ed25519Sha256(public_key=base58.b58decode(alice.public_key)) + bob_ed25519 = Ed25519Sha256(public_key=base58.b58decode(bob.public_key)) + carol_ed25519 = Ed25519Sha256(public_key=base58.b58decode(carol.public_key)) + + threshold = ThresholdSha256(1) + threshold.add_subfulfillment(alice_ed25519) + + sub_threshold = ThresholdSha256(2) + sub_threshold.add_subfulfillment(bob_ed25519) + sub_threshold.add_subfulfillment(carol_ed25519) + + threshold.add_subfulfillment(sub_threshold) + + condition_uri = threshold.condition.serialize_uri() + condition_details = prepare_condition_details(threshold) + + # Assemble output and input for the handcrafted tx + output = { + 'amount': '1', + 'condition': { + 'details': condition_details, + 'uri': condition_uri, + }, + 'public_keys': (alice.public_key, bob.public_key, carol.public_key), + } + + # The yet to be fulfilled input: + input_ = { + 'fulfillment': None, + 'fulfills': None, + 'owners_before': (alice.public_key, bob.public_key), + } + + # Assemble the handcrafted transaction + handcrafted_tx = { + 'operation': 'CREATE', + 'asset': asset, + 'metadata': None, + 'outputs': (output,), + 'inputs': (input_,), + 'version': '2.0', + 'id': None, + } + + # Create sha3-256 of message to sign + message = json.dumps( + handcrafted_tx, + sort_keys=True, + separators=(',', ':'), + ensure_ascii=False, + ) + message = sha3.sha3_256(message.encode()) + + # Sign message with Alice's und Bob's private key + alice_ed25519.sign(message.digest(), base58.b58decode(alice.private_key)) + + # Create fulfillment and add uri to inputs + sub_fulfillment_threshold = ThresholdSha256(2) + sub_fulfillment_threshold.add_subcondition(bob_ed25519.condition) + sub_fulfillment_threshold.add_subcondition(carol_ed25519.condition) + + fulfillment_threshold = ThresholdSha256(1) + fulfillment_threshold.add_subfulfillment(alice_ed25519) + fulfillment_threshold.add_subfulfillment(sub_fulfillment_threshold) + + fulfillment_uri = fulfillment_threshold.serialize_uri() + + handcrafted_tx['inputs'][0]['fulfillment'] = fulfillment_uri + + # Create tx_id for handcrafted_dw_tx and send tx commit + json_str_tx = json.dumps( + handcrafted_tx, + sort_keys=True, + separators=(',', ':'), + ensure_ascii=False, + ) + + creation_tx_id = sha3.sha3_256(json_str_tx.encode()).hexdigest() + + handcrafted_tx['id'] = creation_tx_id + + pm.transactions.send_commit(handcrafted_tx) + + time.sleep(1) + + # Assert that the tx is propagated to all nodes + hosts.assert_transaction(creation_tx_id) + + # Now transfer created asset + alice_transfer_ed25519 = Ed25519Sha256(public_key=base58.b58decode(alice.public_key)) + bob_transfer_ed25519 = Ed25519Sha256(public_key=base58.b58decode(bob.public_key)) + carol_transfer_ed25519 = Ed25519Sha256(public_key=base58.b58decode(carol.public_key)) + + transfer_condition_uri = alice_transfer_ed25519.condition.serialize_uri() + + # Assemble output and input for the handcrafted tx + transfer_output = { + 'amount': '1', + 'condition': { + 'details': { + 'type': alice_transfer_ed25519.TYPE_NAME, + 'public_key': base58.b58encode(alice_transfer_ed25519.public_key).decode() + }, + 'uri': transfer_condition_uri, + }, + 'public_keys': (alice.public_key,), + } + + # The yet to be fulfilled input: + transfer_input_ = { + 'fulfillment': None, + 'fulfills': { + 'transaction_id': creation_tx_id, + 'output_index': 0 + }, + 'owners_before': (alice.public_key, bob.public_key, carol.public_key), + } + + # Assemble the handcrafted transaction + handcrafted_transfer_tx = { + 'operation': 'TRANSFER', + 'asset': {'id': creation_tx_id}, + 'metadata': None, + 'outputs': (transfer_output,), + 'inputs': (transfer_input_,), + 'version': '2.0', + 'id': None, + } + + # Create sha3-256 of message to sign + message = json.dumps( + handcrafted_transfer_tx, + sort_keys=True, + separators=(',', ':'), + ensure_ascii=False, + ) + message = sha3.sha3_256(message.encode()) + + message.update('{}{}'.format( + handcrafted_transfer_tx['inputs'][0]['fulfills']['transaction_id'], + handcrafted_transfer_tx['inputs'][0]['fulfills']['output_index']).encode()) + + # Sign message with Alice's und Bob's private key + bob_transfer_ed25519.sign(message.digest(), base58.b58decode(bob.private_key)) + carol_transfer_ed25519.sign(message.digest(), base58.b58decode(carol.private_key)) + + sub_fulfillment_threshold = ThresholdSha256(2) + sub_fulfillment_threshold.add_subfulfillment(bob_transfer_ed25519) + sub_fulfillment_threshold.add_subfulfillment(carol_transfer_ed25519) + + # Create fulfillment and add uri to inputs + fulfillment_threshold = ThresholdSha256(1) + fulfillment_threshold.add_subcondition(alice_transfer_ed25519.condition) + fulfillment_threshold.add_subfulfillment(sub_fulfillment_threshold) + + fulfillment_uri = fulfillment_threshold.serialize_uri() + + handcrafted_transfer_tx['inputs'][0]['fulfillment'] = fulfillment_uri + + # Create tx_id for handcrafted_dw_tx and send tx commit + json_str_tx = json.dumps( + handcrafted_transfer_tx, + sort_keys=True, + separators=(',', ':'), + ensure_ascii=False, + ) + + transfer_tx_id = sha3.sha3_256(json_str_tx.encode()).hexdigest() + + handcrafted_transfer_tx['id'] = transfer_tx_id + + pm.transactions.send_commit(handcrafted_transfer_tx) + + time.sleep(1) + + # Assert that the tx is propagated to all nodes + hosts.assert_transaction(transfer_tx_id) diff --git a/integration/python/src/test_zenroom.py b/integration/python/src/test_zenroom.py new file mode 100644 index 0000000..8f749a6 --- /dev/null +++ b/integration/python/src/test_zenroom.py @@ -0,0 +1,84 @@ +# GOAL: +# In this script I tried to implement the ECDSA signature using zenroom + +# However, the scripts are customizable and so with the same procedure +# we can implement more complex smart contracts + +# PUBLIC IDENTITY +# The public identity of the users in this script (Bob and Alice) +# is the pair (ECDH public key, Testnet address) + +import json + +from cryptoconditions import ZenroomSha256 +from json.decoder import JSONDecodeError + + +def test_zenroom(gen_key_zencode, secret_key_to_private_key_zencode, fulfill_script_zencode, + condition_script_zencode, zenroom_data, zenroom_house_assets): + alice = json.loads(ZenroomSha256.run_zenroom(gen_key_zencode).output)['keys'] + bob = json.loads(ZenroomSha256.run_zenroom(gen_key_zencode).output)['keys'] + + zen_public_keys = json.loads(ZenroomSha256.run_zenroom(secret_key_to_private_key_zencode.format('Alice'), + keys={'keys': alice}).output) + zen_public_keys.update(json.loads(ZenroomSha256.run_zenroom(secret_key_to_private_key_zencode.format('Bob'), + keys={'keys': bob}).output)) + + # CRYPTO-CONDITIONS: instantiate an Ed25519 crypto-condition for buyer + zenSha = ZenroomSha256(script=fulfill_script_zencode, keys=zen_public_keys, data=zenroom_data) + + # CRYPTO-CONDITIONS: generate the condition uri + condition_uri = zenSha.condition.serialize_uri() + + # CRYPTO-CONDITIONS: construct an unsigned fulfillment dictionary + unsigned_fulfillment_dict = { + 'type': zenSha.TYPE_NAME, + 'script': fulfill_script_zencode, + 'keys': zen_public_keys, + } + + output = { + 'amount': '1000', + 'condition': { + 'details': unsigned_fulfillment_dict, + 'uri': condition_uri, + }, + 'data': zenroom_data, + 'script': fulfill_script_zencode, + 'conf': '', + 'public_keys': (zen_public_keys['Alice']['ecdh_public_key'], ), + } + + input_ = { + 'fulfillment': None, + 'fulfills': None, + 'owners_before': (zen_public_keys['Alice']['ecdh_public_key'], ), + } + + token_creation_tx = { + 'operation': 'CREATE', + 'asset': zenroom_house_assets, + 'metadata': None, + 'outputs': (output,), + 'inputs': (input_,), + 'version': '2.0', + 'id': None, + } + + # JSON: serialize the transaction-without-id to a json formatted string + message = json.dumps( + token_creation_tx, + sort_keys=True, + separators=(',', ':'), + ensure_ascii=False, + ) + + try: + assert(not zenSha.validate(message=message)) + except JSONDecodeError: + pass + except ValueError: + pass + + message = zenSha.sign(message, condition_script_zencode, alice) + assert(zenSha.validate(message=message)) diff --git a/integration/scripts/all-in-one.bash b/integration/scripts/all-in-one.bash new file mode 100755 index 0000000..e719587 --- /dev/null +++ b/integration/scripts/all-in-one.bash @@ -0,0 +1,17 @@ +#!/bin/bash +# Copyright © 2020 Interplanetary Database Association e.V., +# Planetmint and IPDB software contributors. +# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +# Code is Apache-2.0 and docs are CC-BY-4.0 + + +# MongoDB configuration +[ "$(stat -c %U /data/db)" = mongodb ] || chown -R mongodb /data/db + +# Planetmint configuration +/usr/src/app/scripts/planetmint-monit-config + +nohup mongod --bind_ip_all > "$HOME/.planetmint-monit/logs/mongodb_log_$(date +%Y%m%d_%H%M%S)" 2>&1 & + +# Start services +monit -d 5 -I -B \ No newline at end of file diff --git a/integration/scripts/clean-shared.sh b/integration/scripts/clean-shared.sh new file mode 100755 index 0000000..7ba481e --- /dev/null +++ b/integration/scripts/clean-shared.sh @@ -0,0 +1,11 @@ +#!/bin/sh +# Copyright © 2020 Interplanetary Database Association e.V., +# Planetmint and IPDB software contributors. +# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +# Code is Apache-2.0 and docs are CC-BY-4.0 + +rm /shared/hostnames +rm /shared/lock +rm /shared/*node_id +rm /shared/*.json +rm /shared/id_rsa.pub \ No newline at end of file diff --git a/integration/scripts/election.sh b/integration/scripts/election.sh new file mode 100755 index 0000000..65d39cb --- /dev/null +++ b/integration/scripts/election.sh @@ -0,0 +1,81 @@ +#!/bin/bash +# Copyright © 2020 Interplanetary Database Association e.V., +# Planetmint and IPDB software contributors. +# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +# Code is Apache-2.0 and docs are CC-BY-4.0 + +# Show tendermint node id +show_id () { + tendermint --home=/tendermint show_node_id | tail -n 1 +} + +# Show validator public key +show_validator () { + tendermint --home=/tendermint show_validator | tail -n 1 +} + +# Elect new voting power for node +elect_validator () { + planetmint election new upsert-validator $1 $2 $3 --private-key /tendermint/config/priv_validator_key.json 2>&1 +} + +# Propose new chain migration +propose_migration () { + planetmint election new chain-migration --private-key /tendermint/config/priv_validator_key.json 2>&1 +} + +# Show election state +show_election () { + planetmint election show $1 2>&1 +} + +# Approve election +approve_validator () { + planetmint election approve $1 --private-key /tendermint/config/priv_validator_key.json +} + +# Fetch tendermint id and pubkey and create upsert proposal +elect () { + node_id=$(show_id) + validator_pubkey=$(show_validator | jq -r .value) + proposal=$(elect_validator $validator_pubkey $1 $node_id | grep SUCCESS) + echo ${proposal##* } +} + +# Create chain migration proposal and return election id +migrate () { + proposal=$(propose_migration | grep SUCCESS) + echo ${proposal##* } +} + +usage () { + echo "usage: TODO" +} + +while [ "$1" != "" ]; do + case $1 in + show_id ) show_id + ;; + show_validator ) show_validator + ;; + elect ) shift + elect $1 + ;; + migrate ) shift + migrate + ;; + show_election ) shift + show_election $1 + ;; + approve ) shift + approve_validator $1 + ;; + * ) usage + exit 1 + esac + shift +done + +exitcode=$? + +exit $exitcode \ No newline at end of file diff --git a/integration/scripts/genesis.py b/integration/scripts/genesis.py new file mode 100755 index 0000000..3593f34 --- /dev/null +++ b/integration/scripts/genesis.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 +# Copyright © 2020 Interplanetary Database Association e.V., +# Planetmint and IPDB software contributors. +# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +# Code is Apache-2.0 and docs are CC-BY-4.0 + +import json +import sys + + +def edit_genesis() -> None: + file_names = sys.argv[1:] + + validators = [] + for file_name in file_names: + file = open(file_name) + genesis = json.load(file) + validators.extend(genesis['validators']) + file.close() + + genesis_file = open(file_names[0]) + genesis_json = json.load(genesis_file) + genesis_json['validators'] = validators + genesis_file.close() + + with open('/shared/genesis.json', 'w') as f: + json.dump(genesis_json, f, indent=True) + + return None + + +if __name__ == '__main__': + edit_genesis() diff --git a/integration/scripts/planetmint-monit-config b/integration/scripts/planetmint-monit-config new file mode 100755 index 0000000..82af9af --- /dev/null +++ b/integration/scripts/planetmint-monit-config @@ -0,0 +1,208 @@ +#!/bin/bash +# Copyright © 2020 Interplanetary Database Association e.V., +# Planetmint and IPDB software contributors. +# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +# Code is Apache-2.0 and docs are CC-BY-4.0 + +set -o nounset + +# Check if directory for monit logs exists +if [ ! -d "$HOME/.planetmint-monit" ]; then + mkdir -p "$HOME/.planetmint-monit" +fi + +monit_pid_path=${MONIT_PID_PATH:=$HOME/.planetmint-monit/monit_processes} +monit_script_path=${MONIT_SCRIPT_PATH:=$HOME/.planetmint-monit/monit_script} +monit_log_path=${MONIT_LOG_PATH:=$HOME/.planetmint-monit/logs} +monitrc_path=${MONITRC_PATH:=$HOME/.monitrc} + +function usage() { + cat <${monit_script_path} < /dev/null 2>&1 & + + echo \$! > \$2 + popd + + ;; + + stop_planetmint) + + kill -2 \`cat \$2\` + rm -f \$2 + + ;; + + start_tendermint) + + pushd \$4 + + nohup tendermint node \ + --p2p.laddr "tcp://0.0.0.0:26656" \ + --rpc.laddr "tcp://0.0.0.0:26657" \ + --proxy_app="tcp://0.0.0.0:26658" \ + --consensus.create_empty_blocks=false \ + --p2p.pex=false >> \$3/tendermint.out.log 2>> \$3/tendermint.err.log & + + echo \$! > \$2 + popd + + ;; + + stop_tendermint) + + kill -2 \`cat \$2\` + rm -f \$2 + + ;; + +esac +exit 0 +EOF +chmod +x ${monit_script_path} + +cat >${monit_script_path}_logrotate <${monitrc_path} < 200 MB then + exec "${monit_script_path}_logrotate rotate_tendermint_logs ${monit_log_path}/tendermint.out.log $monit_pid_path/tendermint.pid" + +check file tendermint.err.log with path ${monit_log_path}/tendermint.err.log + if size > 200 MB then + exec "${monit_script_path}_logrotate rotate_tendermint_logs ${monit_log_path}/tendermint.err.log $monit_pid_path/tendermint.pid" + +EOF + +# Setting permissions for control file +chmod 0700 ${monitrc_path} + +echo -e "Planetmint process manager configured!" +set -o errexit diff --git a/integration/scripts/pre-config-planetmint.sh b/integration/scripts/pre-config-planetmint.sh new file mode 100755 index 0000000..ea15ea7 --- /dev/null +++ b/integration/scripts/pre-config-planetmint.sh @@ -0,0 +1,83 @@ +#!/bin/bash +# Copyright © 2020 Interplanetary Database Association e.V., +# Planetmint and IPDB software contributors. +# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +# Code is Apache-2.0 and docs are CC-BY-4.0 + +# Write hostname to list +echo $(hostname) >> /shared/hostnames + +# Create ssh folder +mkdir ~/.ssh + +# Wait for test container pubkey +while [ ! -f /shared/id_rsa.pub ]; do + echo "WAIT FOR PUBKEY" + sleep 1 +done + +# Add pubkey to authorized keys +cat /shared/id_rsa.pub > ~/.ssh/authorized_keys + +# Allow root user login +sed -i "s/#PermitRootLogin prohibit-password/PermitRootLogin yes/" /etc/ssh/sshd_config + +# Restart ssh service +service ssh restart + +# Tendermint configuration +tendermint init + +# Write node id to shared folder +HOSTNAME=$(hostname) +NODE_ID=$(tendermint show_node_id | tail -n 1) +echo $NODE_ID > /shared/${HOSTNAME}_node_id + +# Wait for other node ids +FILES=() +while [ ! ${#FILES[@]} == $SCALE ]; do + echo "WAIT FOR NODE IDS" + sleep 1 + FILES=(/shared/*node_id) +done + +# Write node ids to persistent peers +PEERS="persistent_peers = \"" +for f in ${FILES[@]}; do + ID=$(cat $f) + HOST=$(echo $f | cut -c 9-20) + if [ ! $HOST == $HOSTNAME ]; then + PEERS+="${ID}@${HOST}:26656, " + fi +done +PEERS=$(echo $PEERS | rev | cut -c 2- | rev) +PEERS+="\"" +sed -i "/persistent_peers = \"\"/c\\${PEERS}" /tendermint/config/config.toml + +# Copy genesis.json to shared folder +cp /tendermint/config/genesis.json /shared/${HOSTNAME}_genesis.json + +# Await config file of all services to be present +FILES=() +while [ ! ${#FILES[@]} == $SCALE ]; do + echo "WAIT FOR GENESIS FILES" + sleep 1 + FILES=(/shared/*_genesis.json) +done + +# Create genesis.json for nodes +if [ ! -f /shared/lock ]; then + echo LOCKING + touch /shared/lock + /usr/src/app/scripts/genesis.py ${FILES[@]} +fi + +while [ ! -f /shared/genesis.json ]; do + echo "WAIT FOR GENESIS" + sleep 1 +done + +# Copy genesis.json to tendermint config +cp /shared/genesis.json /tendermint/config/genesis.json + +exec "$@" \ No newline at end of file diff --git a/integration/scripts/pre-config-test.sh b/integration/scripts/pre-config-test.sh new file mode 100755 index 0000000..bd72913 --- /dev/null +++ b/integration/scripts/pre-config-test.sh @@ -0,0 +1,16 @@ +#!/bin/bash +# Copyright © 2020 Interplanetary Database Association e.V., +# Planetmint and IPDB software contributors. +# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +# Code is Apache-2.0 and docs are CC-BY-4.0 + +# Create ssh folder +mkdir ~/.ssh + +# Create ssh keys +ssh-keygen -q -t rsa -N '' -f ~/.ssh/id_rsa + +# Publish pubkey to shared folder +cp ~/.ssh/id_rsa.pub /shared + +exec "$@" \ No newline at end of file diff --git a/integration/scripts/test.sh b/integration/scripts/test.sh new file mode 100755 index 0000000..2d3b796 --- /dev/null +++ b/integration/scripts/test.sh @@ -0,0 +1,24 @@ +#!/bin/bash +# Copyright © 2020 Interplanetary Database Association e.V., +# Planetmint and IPDB software contributors. +# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +# Code is Apache-2.0 and docs are CC-BY-4.0 + +# Start CLI Tests + +# Test upsert new validator +/tests/upsert-new-validator.sh + +# Test chain migration +# TODO: implementation not finished +#/tests/chain-migration.sh + +# TODO: Implement test for voting edge cases or implicit in chain migration and upsert validator? + +exitcode=$? + +if [ $exitcode -ne 0 ]; then + exit $exitcode +fi + +exec "$@" \ No newline at end of file diff --git a/integration/scripts/wait-for-planetmint.sh b/integration/scripts/wait-for-planetmint.sh new file mode 100755 index 0000000..36c7794 --- /dev/null +++ b/integration/scripts/wait-for-planetmint.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# Copyright © 2020 Interplanetary Database Association e.V., +# Planetmint and IPDB software contributors. +# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +# Code is Apache-2.0 and docs are CC-BY-4.0 + +# Only continue if all services are ready +HOSTNAMES=() +while [ ! ${#HOSTNAMES[@]} == $SCALE ]; do + echo "WAIT FOR HOSTNAMES" + sleep 1 + readarray -t HOSTNAMES < /shared/hostnames +done + +for host in ${HOSTNAMES[@]}; do + while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' $host:9984)" != "200" ]]; do + echo "WAIT FOR PLANETMINT $host" + sleep 1 + done +done + +for host in ${HOSTNAMES[@]}; do + while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' $host:26657)" != "200" ]]; do + echo "WAIT FOR TENDERMINT $host" + sleep 1 + done +done + +exec "$@" \ No newline at end of file diff --git a/k8s/planetmint/tendermint_container/Dockerfile b/k8s/planetmint/tendermint_container/Dockerfile index 1404f46..91230fc 100644 --- a/k8s/planetmint/tendermint_container/Dockerfile +++ b/k8s/planetmint/tendermint_container/Dockerfile @@ -1,4 +1,4 @@ -FROM tendermint/tendermint:v0.31.5 +FROM tendermint/tendermint:v0.34.15 LABEL maintainer "contact@ipdb.global" WORKDIR / USER root diff --git a/pkg/scripts/all-in-one.bash b/pkg/scripts/all-in-one.bash index 1ccd68d..1ec4371 100755 --- a/pkg/scripts/all-in-one.bash +++ b/pkg/scripts/all-in-one.bash @@ -16,4 +16,5 @@ nohup mongod --bind_ip_all > "$HOME/.planetmint-monit/logs/mongodb_log_$(date +% # Tendermint configuration tendermint init -monit -d 5 -I -B +# Start services +monit -d 5 -I -B \ No newline at end of file diff --git a/pkg/scripts/stack.sh b/pkg/scripts/stack.sh index a00eb72..ea774a0 100755 --- a/pkg/scripts/stack.sh +++ b/pkg/scripts/stack.sh @@ -17,7 +17,7 @@ stack_size=${STACK_SIZE:=4} stack_type=${STACK_TYPE:="docker"} stack_type_provider=${STACK_TYPE_PROVIDER:=""} # NOTE versions prior v0.28.0 have different priv_validator format! -tm_version=${TM_VERSION:="v0.31.5"} +tm_version=${TM_VERSION:="v0.34.15"} mongo_version=${MONGO_VERSION:="3.6"} stack_vm_memory=${STACK_VM_MEMORY:=2048} stack_vm_cpus=${STACK_VM_CPUS:=2} diff --git a/planetmint/__init__.py b/planetmint/__init__.py index a4768ac..36c755a 100644 --- a/planetmint/__init__.py +++ b/planetmint/__init__.py @@ -3,14 +3,15 @@ # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) # Code is Apache-2.0 and docs are CC-BY-4.0 -from planetmint.common.transaction import Transaction # noqa +from planetmint.transactions.common.transaction import Transaction # noqa from planetmint import models # noqa from planetmint.upsert_validator import ValidatorElection # noqa -from planetmint.elections.vote import Vote # noqa +from planetmint.transactions.types.elections.vote import Vote # noqa from planetmint.migrations.chain_migration_election import ChainMigrationElection from planetmint.lib import Planetmint from planetmint.core import App + Transaction.register_type(Transaction.CREATE, models.Transaction) Transaction.register_type(Transaction.TRANSFER, models.Transaction) Transaction.register_type(ValidatorElection.OPERATION, ValidatorElection) diff --git a/planetmint/backend/connection.py b/planetmint/backend/connection.py index 21b3d90..c001ef0 100644 --- a/planetmint/backend/connection.py +++ b/planetmint/backend/connection.py @@ -6,7 +6,9 @@ import logging from importlib import import_module from planetmint.config import Config -from planetmint.common.exceptions import ConfigurationError +from planetmint.backend.exceptions import ConnectionError +from planetmint.transactions.common.exceptions import ConfigurationError + BACKENDS = { # This is path to MongoDBClass 'tarantool_db': 'planetmint.backend.tarantool.connection.TarantoolDB', diff --git a/planetmint/backend/connection_tarantool.py b/planetmint/backend/connection_tarantool.py index dd42014..f39bbad 100644 --- a/planetmint/backend/connection_tarantool.py +++ b/planetmint/backend/connection_tarantool.py @@ -13,7 +13,7 @@ import os from planetmint.backend.exceptions import ConnectionError from planetmint.backend.utils import get_planetmint_config_value, get_planetmint_config_value_or_key_error -from planetmint.common.exceptions import ConfigurationError +from planetmint.transactions.common.exceptions import ConfigurationError BACKENDS = { # This is path to MongoDBClass 'tarantool': 'planetmint.backend.connection_tarantool.TarantoolDB', diff --git a/planetmint/backend/localmongodb/connection.py b/planetmint/backend/localmongodb/connection.py index 58e9721..9c55157 100644 --- a/planetmint/backend/localmongodb/connection.py +++ b/planetmint/backend/localmongodb/connection.py @@ -11,7 +11,7 @@ from planetmint.config import Config from planetmint.backend.exceptions import (DuplicateKeyError, OperationError, ConnectionError) -from planetmint.common.exceptions import ConfigurationError +from planetmint.transactions.common.exceptions import ConfigurationError from planetmint.utils import Lazy logger = logging.getLogger(__name__) diff --git a/planetmint/backend/localmongodb/query.py b/planetmint/backend/localmongodb/query.py index 4402c4e..6d103f5 100644 --- a/planetmint/backend/localmongodb/query.py +++ b/planetmint/backend/localmongodb/query.py @@ -12,7 +12,7 @@ from planetmint import backend from planetmint.backend.exceptions import DuplicateKeyError from planetmint.backend.utils import module_dispatch_registrar from planetmint.backend.localmongodb.connection import LocalMongoDBConnection -from planetmint.common.transaction import Transaction +from planetmint.transactions.common.transaction import Transaction register_query = module_dispatch_registrar(backend.query) diff --git a/planetmint/backend/schema.py b/planetmint/backend/schema.py index fbb0180..2677df0 100644 --- a/planetmint/backend/schema.py +++ b/planetmint/backend/schema.py @@ -10,8 +10,9 @@ import logging from planetmint.config import Config from planetmint.backend.connection import Connection -from planetmint.common.exceptions import ValidationError -from planetmint.common.utils import validate_all_values_for_key_in_obj, validate_all_values_for_key_in_list +from planetmint.transactions.common.exceptions import ValidationError +from planetmint.transactions.common.utils import ( + validate_all_values_for_key_in_obj, validate_all_values_for_key_in_list) logger = logging.getLogger(__name__) diff --git a/planetmint/backend/tarantool/connection.py b/planetmint/backend/tarantool/connection.py index 9ebf247..06a48ec 100644 --- a/planetmint/backend/tarantool/connection.py +++ b/planetmint/backend/tarantool/connection.py @@ -7,7 +7,7 @@ import logging import tarantool from planetmint.config import Config -from planetmint.common.exceptions import ConfigurationError +from planetmint.transactions.common.exceptions import ConfigurationError logger = logging.getLogger(__name__) diff --git a/planetmint/backend/tarantool/transaction/tools.py b/planetmint/backend/tarantool/transaction/tools.py index e4dc74b..5660eef 100644 --- a/planetmint/backend/tarantool/transaction/tools.py +++ b/planetmint/backend/tarantool/transaction/tools.py @@ -1,6 +1,6 @@ from secrets import token_hex import copy -from planetmint.common.memoize import HDict +from planetmint.transactions.common.memoize import HDict def get_items(_list): diff --git a/planetmint/commands/planetmint.py b/planetmint/commands/planetmint.py index 752a5af..154677f 100644 --- a/planetmint/commands/planetmint.py +++ b/planetmint/commands/planetmint.py @@ -18,10 +18,10 @@ from planetmint.backend.tarantool.connection import TarantoolDB from planetmint.core import rollback from planetmint.migrations.chain_migration_election import ChainMigrationElection from planetmint.utils import load_node_key -from planetmint.common.transaction_mode_types import BROADCAST_TX_COMMIT -from planetmint.common.exceptions import (DatabaseDoesNotExist, - ValidationError) -from planetmint.elections.vote import Vote +from planetmint.transactions.common.transaction_mode_types import BROADCAST_TX_COMMIT +from planetmint.transactions.common.exceptions import ( + DatabaseDoesNotExist, ValidationError) +from planetmint.transactions.types.elections.vote import Vote import planetmint from planetmint import (backend, ValidatorElection, Planetmint) diff --git a/planetmint/config_utils.py b/planetmint/config_utils.py index 513f55b..c3ac0e4 100644 --- a/planetmint/config_utils.py +++ b/planetmint/config_utils.py @@ -24,8 +24,8 @@ import collections.abc from functools import lru_cache from pkg_resources import iter_entry_points, ResolutionError -from planetmint.common import exceptions from planetmint.config import Config +from planetmint.transactions.common import exceptions from planetmint.validation import BaseValidationRules # TODO: move this to a proper configuration file for logging diff --git a/planetmint/core.py b/planetmint/core.py index e9d2160..c7b1607 100644 --- a/planetmint/core.py +++ b/planetmint/core.py @@ -8,14 +8,20 @@ with Tendermint. """ import logging import sys - +from tendermint.abci import types_pb2 from abci.application import BaseApplication -from abci import CodeTypeOk - +from abci.application import OkCode +from tendermint.abci.types_pb2 import ( + ResponseInfo, + ResponseInitChain, + ResponseCheckTx, + ResponseDeliverTx, + ResponseBeginBlock, + ResponseEndBlock, + ResponseCommit +) from planetmint import Planetmint -from planetmint.elections.election import Election -from planetmint.version import __tm_supported_versions__ -from planetmint.utils import tendermint_version_is_compatible +from planetmint.transactions.types.elections.election import Election from planetmint.tendermint_utils import (decode_transaction, calculate_hash) from planetmint.lib import Block @@ -34,39 +40,36 @@ class App(BaseApplication): transaction logic to Tendermint Core. """ - def __init__(self, abci, planetmint=None, events_queue=None,): - super().__init__(abci) + def __init__(self, planetmint_node=None, events_queue=None): + # super().__init__(abci) + logger.debug('Checking values of types') + logger.debug(dir(types_pb2)) self.events_queue = events_queue - self.planetmint = planetmint or Planetmint() + self.planetmint_node = planetmint_node or Planetmint() self.block_txn_ids = [] self.block_txn_hash = '' self.block_transactions = [] self.validators = None self.new_height = None - self.chain = self.planetmint.get_latest_abci_chain() - print( f"chain: {self.chain}") - + self.chain = self.planetmint_node.get_latest_abci_chain() + def log_abci_migration_error(self, chain_id, validators): logger.error('An ABCI chain migration is in process. ' - 'Download the new ABCI client and configure it with ' + 'Download theself.planetmint_node.get_latest_abci_chain new ABCI client and configure it with ' f'chain_id={chain_id} and validators={validators}.') def abort_if_abci_chain_is_not_synced(self): if self.chain is None or self.chain['is_synced']: return - - validators = self.planetmint.get_validators() + validators = self.planetmint_node.get_validators() self.log_abci_migration_error(self.chain['chain_id'], validators) sys.exit(1) def init_chain(self, genesis): """Initialize chain upon genesis or a migration""" - app_hash = '' height = 0 - - known_chain = self.planetmint.get_latest_abci_chain() - print( f" known_chain: {known_chain}") + known_chain = self.planetmint_node.get_latest_abci_chain() if known_chain is not None: chain_id = known_chain['chain_id'] @@ -75,35 +78,29 @@ class App(BaseApplication): f'the chain {chain_id} is already synced.') logger.error(msg) sys.exit(1) - if chain_id != genesis.chain_id: - validators = self.planetmint.get_validators() + validators = self.planetmint_node.get_validators() self.log_abci_migration_error(chain_id, validators) sys.exit(1) - # set migration values for app hash and height - block = self.planetmint.get_latest_block() + block = self.planetmint_node.get_latest_block() app_hash = '' if block is None else block['app_hash'] height = 0 if block is None else block['height'] + 1 - - known_validators = self.planetmint.get_validators() + known_validators = self.planetmint_node.get_validators() validator_set = [vutils.decode_validator(v) for v in genesis.validators] - if known_validators and known_validators != validator_set: self.log_abci_migration_error(known_chain['chain_id'], known_validators) sys.exit(1) - block = Block(app_hash=app_hash, height=height, transactions=[]) - self.planetmint.store_block(block._asdict()) - self.planetmint.store_validator_set(height + 1, validator_set) + self.planetmint_node.store_block(block._asdict()) + self.planetmint_node.store_validator_set(height + 1, validator_set) abci_chain_height = 0 if known_chain is None else known_chain['height'] - self.planetmint.store_abci_chain(abci_chain_height, - genesis.chain_id, True) + self.planetmint_node.store_abci_chain(abci_chain_height, genesis.chain_id, True) self.chain = {'height': abci_chain_height, 'is_synced': True, 'chain_id': genesis.chain_id} - return self.abci.ResponseInitChain() + return ResponseInitChain() def info(self, request): """Return height of the latest committed block.""" @@ -111,15 +108,15 @@ class App(BaseApplication): self.abort_if_abci_chain_is_not_synced() # Check if Planetmint supports the Tendermint version - if not (hasattr(request, 'version') and tendermint_version_is_compatible(request.version)): - logger.error(f'Unsupported Tendermint version: {getattr(request, "version", "no version")}.' - f' Currently, Planetmint only supports {__tm_supported_versions__}. Exiting!') - sys.exit(1) + # if not (hasattr(request, 'version') and tendermint_version_is_compatible(request.version)): + # logger.error(f'Unsupported Tendermint version: {getattr(request, "version", "no version")}.' + # f' Currently, Planetmint only supports {__tm_supported_versions__}. Exiting!') + # sys.exit(1) - logger.info(f"Tendermint version: {request.version}") + # logger.info(f"Tendermint version: {request.version}") - r = self.abci.ResponseInfo() - block = self.planetmint.get_latest_block() + r = ResponseInfo() + block = self.planetmint_node.get_latest_block() if block: chain_shift = 0 if self.chain is None else self.chain['height'] r.last_block_height = block['height'] - chain_shift @@ -141,12 +138,12 @@ class App(BaseApplication): logger.debug('check_tx: %s', raw_transaction) transaction = decode_transaction(raw_transaction) - if self.planetmint.is_valid_transaction(transaction): + if self.planetmint_node.is_valid_transaction(transaction): logger.debug('check_tx: VALID') - return self.abci.ResponseCheckTx(code=CodeTypeOk) + return ResponseCheckTx(code=OkCode) else: logger.debug('check_tx: INVALID') - return self.abci.ResponseCheckTx(code=CodeTypeError) + return ResponseCheckTx(code=CodeTypeError) def begin_block(self, req_begin_block): """Initialize list of transaction. @@ -157,13 +154,13 @@ class App(BaseApplication): self.abort_if_abci_chain_is_not_synced() chain_shift = 0 if self.chain is None else self.chain['height'] - logger.debug('BEGIN BLOCK, height:%s, num_txs:%s', - req_begin_block.header.height + chain_shift, - req_begin_block.header.num_txs) + # req_begin_block.header.num_txs not found, so removing it. + logger.debug('BEGIN BLOCK, height:%s', + req_begin_block.header.height + chain_shift) self.block_txn_ids = [] self.block_transactions = [] - return self.abci.ResponseBeginBlock() + return ResponseBeginBlock() def deliver_tx(self, raw_transaction): """Validate the transaction before mutating the state. @@ -175,17 +172,17 @@ class App(BaseApplication): self.abort_if_abci_chain_is_not_synced() logger.debug('deliver_tx: %s', raw_transaction) - transaction = self.planetmint.is_valid_transaction( + transaction = self.planetmint_node.is_valid_transaction( decode_transaction(raw_transaction), self.block_transactions) if not transaction: logger.debug('deliver_tx: INVALID') - return self.abci.ResponseDeliverTx(code=CodeTypeError) + return ResponseDeliverTx(code=CodeTypeError) else: logger.debug('storing tx') self.block_txn_ids.append(transaction.id) self.block_transactions.append(transaction) - return self.abci.ResponseDeliverTx(code=CodeTypeOk) + return ResponseDeliverTx(code=OkCode) def end_block(self, request_end_block): """Calculate block hash using transaction ids and previous block @@ -207,21 +204,21 @@ class App(BaseApplication): logger.debug(f'Updating pre-commit state: {self.new_height}') pre_commit_state = dict(height=self.new_height, transactions=self.block_txn_ids) - self.planetmint.store_pre_commit_state(pre_commit_state) + self.planetmint_node.store_pre_commit_state(pre_commit_state) block_txn_hash = calculate_hash(self.block_txn_ids) - block = self.planetmint.get_latest_block() + block = self.planetmint_node.get_latest_block() if self.block_txn_ids: self.block_txn_hash = calculate_hash([block['app_hash'], block_txn_hash]) else: self.block_txn_hash = block['app_hash'] - validator_update = Election.process_block(self.planetmint, + validator_update = Election.process_block(self.planetmint_node, self.new_height, self.block_transactions) - return self.abci.ResponseEndBlock(validator_updates=validator_update) + return ResponseEndBlock(validator_updates=validator_update) def commit(self): """Store the new height and along with block hash.""" @@ -232,14 +229,14 @@ class App(BaseApplication): # register a new block only when new transactions are received if self.block_txn_ids: - self.planetmint.store_bulk_transactions(self.block_transactions) + self.planetmint_node.store_bulk_transactions(self.block_transactions) block = Block(app_hash=self.block_txn_hash, height=self.new_height, transactions=self.block_txn_ids) # NOTE: storing the block should be the last operation during commit # this effects crash recovery. Refer BEP#8 for details - self.planetmint.store_block(block._asdict()) + self.planetmint_node.store_block(block._asdict()) logger.debug('Commit-ing new block with hash: apphash=%s ,' 'height=%s, txn ids=%s', data, self.new_height, @@ -252,7 +249,7 @@ class App(BaseApplication): }) self.events_queue.put(event) - return self.abci.ResponseCommit(data=data) + return ResponseCommit(data=data) def rollback(b): diff --git a/planetmint/fastquery.py b/planetmint/fastquery.py index c1aa843..bfbb6a8 100644 --- a/planetmint/fastquery.py +++ b/planetmint/fastquery.py @@ -5,7 +5,7 @@ from planetmint.utils import condition_details_has_owner from planetmint.backend import query -from planetmint.common.transaction import TransactionLink +from planetmint.transactions.common.transaction import TransactionLink class FastQuery(): diff --git a/planetmint/lib.py b/planetmint/lib.py index d92a799..47ec851 100644 --- a/planetmint/lib.py +++ b/planetmint/lib.py @@ -25,30 +25,27 @@ import planetmint from planetmint.config import Config from planetmint import backend, config_utils, fastquery from planetmint.models import Transaction -from planetmint.common.exceptions import (SchemaValidationError, - ValidationError, - DoubleSpend) -from planetmint.common.transaction_mode_types import (BROADCAST_TX_COMMIT, - BROADCAST_TX_ASYNC, - BROADCAST_TX_SYNC) +from planetmint.transactions.common.exceptions import ( + SchemaValidationError, ValidationError, DoubleSpend) +from planetmint.transactions.common.transaction_mode_types import ( + BROADCAST_TX_COMMIT, BROADCAST_TX_ASYNC, BROADCAST_TX_SYNC) from planetmint.tendermint_utils import encode_transaction, merkleroot from planetmint import exceptions as core_exceptions from planetmint.validation import BaseValidationRules logger = logging.getLogger(__name__) - class Planetmint(object): - """Bigchain API + """Planetmint API Create, read, sign, write transactions to the database """ def __init__(self, connection=None): - """Initialize the Bigchain instance + """Initialize the Planetmint instance - A Bigchain instance has several configuration parameters (e.g. host). - If a parameter value is passed as an argument to the Bigchain + A Planetmint instance has several configuration parameters (e.g. host). + If a parameter value is passed as an argument to the Planetmint __init__ method, then that is the value it will have. Otherwise, the parameter value will come from an environment variable. If that environment variable isn't set, then the value diff --git a/planetmint/log.py b/planetmint/log.py index 337f346..335fc9b 100644 --- a/planetmint/log.py +++ b/planetmint/log.py @@ -6,7 +6,7 @@ import planetmint import logging -from planetmint.common.exceptions import ConfigurationError +from planetmint.transactions.common.exceptions import ConfigurationError from logging.config import dictConfig as set_logging_config from planetmint.config import Config, DEFAULT_LOGGING_CONFIG import os diff --git a/planetmint/migrations/chain_migration_election.py b/planetmint/migrations/chain_migration_election.py index d913e84..5e23e40 100644 --- a/planetmint/migrations/chain_migration_election.py +++ b/planetmint/migrations/chain_migration_election.py @@ -1,7 +1,7 @@ import json -from planetmint.common.schema import TX_SCHEMA_CHAIN_MIGRATION_ELECTION -from planetmint.elections.election import Election +from planetmint.transactions.common.schema import TX_SCHEMA_CHAIN_MIGRATION_ELECTION +from planetmint.transactions.types.elections.election import Election class ChainMigrationElection(Election): diff --git a/planetmint/models.py b/planetmint/models.py index 001df55..419cb44 100644 --- a/planetmint/models.py +++ b/planetmint/models.py @@ -4,11 +4,10 @@ # Code is Apache-2.0 and docs are CC-BY-4.0 from planetmint.backend.schema import validate_language_key -from planetmint.common.exceptions import (InvalidSignature, - DuplicateTransaction) -from planetmint.common.schema import validate_transaction_schema -from planetmint.common.transaction import Transaction -from planetmint.common.utils import (validate_txn_obj, validate_key) +from planetmint.transactions.common.exceptions import (InvalidSignature, DuplicateTransaction) +from planetmint.transactions.common.schema import validate_transaction_schema +from planetmint.transactions.common.transaction import Transaction +from planetmint.transactions.common.utils import (validate_txn_obj, validate_key) class Transaction(Transaction): diff --git a/planetmint/parallel_validation.py b/planetmint/parallel_validation.py index 047bb2c..77c4a02 100644 --- a/planetmint/parallel_validation.py +++ b/planetmint/parallel_validation.py @@ -6,23 +6,28 @@ import multiprocessing as mp from collections import defaultdict -from planetmint import App, Planetmint +from planetmint import App +from planetmint.lib import Planetmint from planetmint.tendermint_utils import decode_transaction -from abci import CodeTypeOk +from abci.application import OkCode +from tendermint.abci.types_pb2 import ( + ResponseCheckTx, + ResponseDeliverTx, +) class ParallelValidationApp(App): - def __init__(self, planetmint=None, events_queue=None, abci=None): - super().__init__(planetmint, events_queue, abci=abci) + def __init__(self, planetmint=None, events_queue=None): + super().__init__(planetmint, events_queue) self.parallel_validator = ParallelValidator() self.parallel_validator.start() def check_tx(self, raw_transaction): - return self.abci.ResponseCheckTx(code=CodeTypeOk) + return ResponseCheckTx(code=OkCode) def deliver_tx(self, raw_transaction): self.parallel_validator.validate(raw_transaction) - return self.abci.ResponseDeliverTx(code=CodeTypeOk) + return ResponseDeliverTx(code=OkCode) def end_block(self, request_end_block): result = self.parallel_validator.result(timeout=30) diff --git a/planetmint/start.py b/planetmint/start.py index c5e57ef..7eeb022 100644 --- a/planetmint/start.py +++ b/planetmint/start.py @@ -6,8 +6,6 @@ import logging import setproctitle -from abci import TmVersion, ABCI - from planetmint.config import Config from planetmint.lib import Planetmint from planetmint.core import App @@ -68,18 +66,15 @@ def start(args): setproctitle.setproctitle('planetmint') # Start the ABCIServer - abci = ABCI(TmVersion(Config().get()['tendermint']['version'])) if args.experimental_parallel_validation: app = ABCIServer( app=ParallelValidationApp( - abci=abci.types, events_queue=exchange.get_publisher_queue(), ) ) else: app = ABCIServer( app=App( - abci=abci.types, events_queue=exchange.get_publisher_queue(), ) ) diff --git a/planetmint/transactions/__init__.py b/planetmint/transactions/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/planetmint/transactions/common/__init__.py b/planetmint/transactions/common/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/planetmint/common/crypto.py b/planetmint/transactions/common/crypto.py similarity index 88% rename from planetmint/common/crypto.py rename to planetmint/transactions/common/crypto.py index 72ea536..9205c27 100644 --- a/planetmint/common/crypto.py +++ b/planetmint/transactions/common/crypto.py @@ -26,10 +26,10 @@ def generate_key_pair(): """Generates a cryptographic key pair. Returns: - :class:`~planetmint.common.crypto.CryptoKeypair`: A + :class:`~planetmint.transactions.common.crypto.CryptoKeypair`: A :obj:`collections.namedtuple` with named fields - :attr:`~planetmint.common.crypto.CryptoKeypair.private_key` and - :attr:`~planetmint.common.crypto.CryptoKeypair.public_key`. + :attr:`~planetmint.transactions.common.crypto.CryptoKeypair.private_key` and + :attr:`~planetmint.transactions.common.crypto.CryptoKeypair.public_key`. """ # TODO FOR CC: Adjust interface so that this function becomes unnecessary diff --git a/planetmint/common/exceptions.py b/planetmint/transactions/common/exceptions.py similarity index 100% rename from planetmint/common/exceptions.py rename to planetmint/transactions/common/exceptions.py diff --git a/planetmint/transactions/common/input.py b/planetmint/transactions/common/input.py new file mode 100644 index 0000000..ab123cb --- /dev/null +++ b/planetmint/transactions/common/input.py @@ -0,0 +1,125 @@ +# Copyright © 2020 Interplanetary Database Association e.V., +# Planetmint and IPDB software contributors. +# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +# Code is Apache-2.0 and docs are CC-BY-4.0 + +from cryptoconditions import Fulfillment +from cryptoconditions.exceptions import ASN1DecodeError, ASN1EncodeError + +from planetmint.transactions.common.exceptions import InvalidSignature +from .utils import _fulfillment_to_details, _fulfillment_from_details +from .output import Output +from .transaction_link import TransactionLink + +class Input(object): + """A Input is used to spend assets locked by an Output. + + Wraps around a Crypto-condition Fulfillment. + + Attributes: + fulfillment (:class:`cryptoconditions.Fulfillment`): A Fulfillment + to be signed with a private key. + owners_before (:obj:`list` of :obj:`str`): A list of owners after a + Transaction was confirmed. + fulfills (:class:`~planetmint.transactions.common.transaction. TransactionLink`, + optional): A link representing the input of a `TRANSFER` + Transaction. + """ + + def __init__(self, fulfillment, owners_before, fulfills=None): + """Create an instance of an :class:`~.Input`. + + Args: + fulfillment (:class:`cryptoconditions.Fulfillment`): A + Fulfillment to be signed with a private key. + owners_before (:obj:`list` of :obj:`str`): A list of owners + after a Transaction was confirmed. + fulfills (:class:`~planetmint.transactions.common.transaction. + TransactionLink`, optional): A link representing the input + of a `TRANSFER` Transaction. + """ + if fulfills is not None and not isinstance(fulfills, TransactionLink): + raise TypeError('`fulfills` must be a TransactionLink instance') + if not isinstance(owners_before, list): + raise TypeError('`owners_before` must be a list instance') + + self.fulfillment = fulfillment + self.fulfills = fulfills + self.owners_before = owners_before + + def __eq__(self, other): + # TODO: If `other !== Fulfillment` return `False` + return self.to_dict() == other.to_dict() + + # NOTE: This function is used to provide a unique key for a given + # Input to suppliment memoization + def __hash__(self): + return hash((self.fulfillment, self.fulfills)) + + def to_dict(self): + """Transforms the object to a Python dictionary. + + Note: + If an Input hasn't been signed yet, this method returns a + dictionary representation. + + Returns: + dict: The Input as an alternative serialization format. + """ + try: + fulfillment = self.fulfillment.serialize_uri() + except (TypeError, AttributeError, ASN1EncodeError, ASN1DecodeError): + fulfillment = _fulfillment_to_details(self.fulfillment) + + try: + # NOTE: `self.fulfills` can be `None` and that's fine + fulfills = self.fulfills.to_dict() + except AttributeError: + fulfills = None + + input_ = { + 'owners_before': self.owners_before, + 'fulfills': fulfills, + 'fulfillment': fulfillment, + } + return input_ + + @classmethod + def generate(cls, public_keys): + # TODO: write docstring + # The amount here does not really matter. It is only use on the + # output data model but here we only care about the fulfillment + output = Output.generate(public_keys, 1) + return cls(output.fulfillment, public_keys) + + @classmethod + def from_dict(cls, data): + """Transforms a Python dictionary to an Input object. + + Note: + Optionally, this method can also serialize a Cryptoconditions- + Fulfillment that is not yet signed. + + Args: + data (dict): The Input to be transformed. + + Returns: + :class:`~planetmint.transactions.common.transaction.Input` + + Raises: + InvalidSignature: If an Input's URI couldn't be parsed. + """ + fulfillment = data['fulfillment'] + if not isinstance(fulfillment, (Fulfillment, type(None))): + try: + fulfillment = Fulfillment.from_uri(data['fulfillment']) + except ASN1DecodeError: + # TODO Remove as it is legacy code, and simply fall back on + # ASN1DecodeError + raise InvalidSignature("Fulfillment URI couldn't been parsed") + except TypeError: + # NOTE: See comment about this special case in + # `Input.to_dict` + fulfillment = _fulfillment_from_details(data['fulfillment']) + fulfills = TransactionLink.from_dict(data['fulfills']) + return cls(fulfillment, data['owners_before'], fulfills) diff --git a/planetmint/common/memoize.py b/planetmint/transactions/common/memoize.py similarity index 100% rename from planetmint/common/memoize.py rename to planetmint/transactions/common/memoize.py diff --git a/planetmint/transactions/common/output.py b/planetmint/transactions/common/output.py new file mode 100644 index 0000000..6462941 --- /dev/null +++ b/planetmint/transactions/common/output.py @@ -0,0 +1,208 @@ +# Copyright © 2020 Interplanetary Database Association e.V., +# Planetmint and IPDB software contributors. +# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +# Code is Apache-2.0 and docs are CC-BY-4.0 + +from functools import reduce + +import base58 +from cryptoconditions import Fulfillment, ThresholdSha256, Ed25519Sha256 + +from planetmint.transactions.common.exceptions import AmountError +from .utils import _fulfillment_to_details, _fulfillment_from_details + +class Output(object): + """An Output is used to lock an asset. + + Wraps around a Crypto-condition Condition. + + Attributes: + fulfillment (:class:`cryptoconditions.Fulfillment`): A Fulfillment + to extract a Condition from. + public_keys (:obj:`list` of :obj:`str`, optional): A list of + owners before a Transaction was confirmed. + """ + + MAX_AMOUNT = 9 * 10 ** 18 + + def __init__(self, fulfillment, public_keys=None, amount=1): + """Create an instance of a :class:`~.Output`. + + Args: + fulfillment (:class:`cryptoconditions.Fulfillment`): A + Fulfillment to extract a Condition from. + public_keys (:obj:`list` of :obj:`str`, optional): A list of + owners before a Transaction was confirmed. + amount (int): The amount of Assets to be locked with this + Output. + + Raises: + TypeError: if `public_keys` is not instance of `list`. + """ + if not isinstance(public_keys, list) and public_keys is not None: + raise TypeError('`public_keys` must be a list instance or None') + if not isinstance(amount, int): + raise TypeError('`amount` must be an int') + if amount < 1: + raise AmountError('`amount` must be greater than 0') + if amount > self.MAX_AMOUNT: + raise AmountError('`amount` must be <= %s' % self.MAX_AMOUNT) + + self.fulfillment = fulfillment + self.amount = amount + self.public_keys = public_keys + + def __eq__(self, other): + # TODO: If `other !== Condition` return `False` + return self.to_dict() == other.to_dict() + + def to_dict(self): + """Transforms the object to a Python dictionary. + + Note: + A dictionary serialization of the Input the Output was + derived from is always provided. + + Returns: + dict: The Output as an alternative serialization format. + """ + # TODO FOR CC: It must be able to recognize a hashlock condition + # and fulfillment! + condition = {} + try: + condition['details'] = _fulfillment_to_details(self.fulfillment) + except AttributeError: + pass + + try: + condition['uri'] = self.fulfillment.condition_uri + except AttributeError: + condition['uri'] = self.fulfillment + + output = { + 'public_keys': self.public_keys, + 'condition': condition, + 'amount': str(self.amount), + } + return output + + @classmethod + def generate(cls, public_keys, amount): + """Generates a Output from a specifically formed tuple or list. + + Note: + If a ThresholdCondition has to be generated where the threshold + is always the number of subconditions it is split between, a + list of the following structure is sufficient: + + [(address|condition)*, [(address|condition)*, ...], ...] + + Args: + public_keys (:obj:`list` of :obj:`str`): The public key of + the users that should be able to fulfill the Condition + that is being created. + amount (:obj:`int`): The amount locked by the Output. + + Returns: + An Output that can be used in a Transaction. + + Raises: + TypeError: If `public_keys` is not an instance of `list`. + ValueError: If `public_keys` is an empty list. + """ + threshold = len(public_keys) + if not isinstance(amount, int): + raise TypeError('`amount` must be a int') + if amount < 1: + raise AmountError('`amount` needs to be greater than zero') + if not isinstance(public_keys, list): + raise TypeError('`public_keys` must be an instance of list') + if len(public_keys) == 0: + raise ValueError('`public_keys` needs to contain at least one' + 'owner') + elif len(public_keys) == 1 and not isinstance(public_keys[0], list): + if isinstance(public_keys[0], Fulfillment): + ffill = public_keys[0] + else: + ffill = Ed25519Sha256( + public_key=base58.b58decode(public_keys[0])) + return cls(ffill, public_keys, amount=amount) + else: + initial_cond = ThresholdSha256(threshold=threshold) + threshold_cond = reduce(cls._gen_condition, public_keys, + initial_cond) + return cls(threshold_cond, public_keys, amount=amount) + + @classmethod + def _gen_condition(cls, initial, new_public_keys): + """Generates ThresholdSha256 conditions from a list of new owners. + + Note: + This method is intended only to be used with a reduce function. + For a description on how to use this method, see + :meth:`~.Output.generate`. + + Args: + initial (:class:`cryptoconditions.ThresholdSha256`): + A Condition representing the overall root. + new_public_keys (:obj:`list` of :obj:`str`|str): A list of new + owners or a single new owner. + + Returns: + :class:`cryptoconditions.ThresholdSha256`: + """ + try: + threshold = len(new_public_keys) + except TypeError: + threshold = None + + if isinstance(new_public_keys, list) and len(new_public_keys) > 1: + ffill = ThresholdSha256(threshold=threshold) + reduce(cls._gen_condition, new_public_keys, ffill) + elif isinstance(new_public_keys, list) and len(new_public_keys) <= 1: + raise ValueError('Sublist cannot contain single owner') + else: + try: + new_public_keys = new_public_keys.pop() + except AttributeError: + pass + # NOTE: Instead of submitting base58 encoded addresses, a user + # of this class can also submit fully instantiated + # Cryptoconditions. In the case of casting + # `new_public_keys` to a Ed25519Fulfillment with the + # result of a `TypeError`, we're assuming that + # `new_public_keys` is a Cryptocondition then. + if isinstance(new_public_keys, Fulfillment): + ffill = new_public_keys + else: + ffill = Ed25519Sha256( + public_key=base58.b58decode(new_public_keys)) + initial.add_subfulfillment(ffill) + return initial + + @classmethod + def from_dict(cls, data): + """Transforms a Python dictionary to an Output object. + + Note: + To pass a serialization cycle multiple times, a + Cryptoconditions Fulfillment needs to be present in the + passed-in dictionary, as Condition URIs are not serializable + anymore. + + Args: + data (dict): The dict to be transformed. + + Returns: + :class:`~planetmint.transactions.common.transaction.Output` + """ + try: + fulfillment = _fulfillment_from_details(data['condition']['details']) + except KeyError: + # NOTE: Hashlock condition case + fulfillment = data['condition']['uri'] + try: + amount = int(data['amount']) + except ValueError: + raise AmountError('Invalid amount: %s' % data['amount']) + return cls(fulfillment, data['public_keys'], amount) diff --git a/planetmint/common/schema/README.md b/planetmint/transactions/common/schema/README.md similarity index 100% rename from planetmint/common/schema/README.md rename to planetmint/transactions/common/schema/README.md diff --git a/planetmint/common/schema/__init__.py b/planetmint/transactions/common/schema/__init__.py similarity index 82% rename from planetmint/common/schema/__init__.py rename to planetmint/transactions/common/schema/__init__.py index df28597..51e092c 100644 --- a/planetmint/common/schema/__init__.py +++ b/planetmint/transactions/common/schema/__init__.py @@ -11,37 +11,38 @@ import jsonschema import yaml import rapidjson -from planetmint.common.exceptions import SchemaValidationError +from planetmint.transactions.common.exceptions import SchemaValidationError logger = logging.getLogger(__name__) -def _load_schema(name, path=__file__): +def _load_schema(name, version, path=__file__): """Load a schema from disk""" - path = os.path.join(os.path.dirname(path), name + '.yaml') + path = os.path.join(os.path.dirname(path), version, name + '.yaml') with open(path) as handle: schema = yaml.safe_load(handle) fast_schema = rapidjson.Validator(rapidjson.dumps(schema)) return path, (schema, fast_schema) +# TODO: make this an env var from a config file TX_SCHEMA_VERSION = 'v2.0' -TX_SCHEMA_PATH, TX_SCHEMA_COMMON = _load_schema('transaction_' + +TX_SCHEMA_PATH, TX_SCHEMA_COMMON = _load_schema('transaction', TX_SCHEMA_VERSION) -_, TX_SCHEMA_CREATE = _load_schema('transaction_create_' + +_, TX_SCHEMA_CREATE = _load_schema('transaction_create', TX_SCHEMA_VERSION) -_, TX_SCHEMA_TRANSFER = _load_schema('transaction_transfer_' + +_, TX_SCHEMA_TRANSFER = _load_schema('transaction_transfer', TX_SCHEMA_VERSION) -_, TX_SCHEMA_VALIDATOR_ELECTION = _load_schema('transaction_validator_election_' + +_, TX_SCHEMA_VALIDATOR_ELECTION = _load_schema('transaction_validator_election', TX_SCHEMA_VERSION) -_, TX_SCHEMA_CHAIN_MIGRATION_ELECTION = _load_schema('transaction_chain_migration_election_' + +_, TX_SCHEMA_CHAIN_MIGRATION_ELECTION = _load_schema('transaction_chain_migration_election', TX_SCHEMA_VERSION) -_, TX_SCHEMA_VOTE = _load_schema('transaction_vote_' + TX_SCHEMA_VERSION) +_, TX_SCHEMA_VOTE = _load_schema('transaction_vote', TX_SCHEMA_VERSION) def _validate_schema(schema, body): diff --git a/planetmint/common/schema/transaction_v1.0.yaml b/planetmint/transactions/common/schema/v1.0/transaction.yaml similarity index 100% rename from planetmint/common/schema/transaction_v1.0.yaml rename to planetmint/transactions/common/schema/v1.0/transaction.yaml diff --git a/planetmint/common/schema/transaction_create_v1.0.yaml b/planetmint/transactions/common/schema/v1.0/transaction_create.yaml similarity index 100% rename from planetmint/common/schema/transaction_create_v1.0.yaml rename to planetmint/transactions/common/schema/v1.0/transaction_create.yaml diff --git a/planetmint/common/schema/transaction_transfer_v1.0.yaml b/planetmint/transactions/common/schema/v1.0/transaction_transfer.yaml similarity index 100% rename from planetmint/common/schema/transaction_transfer_v1.0.yaml rename to planetmint/transactions/common/schema/v1.0/transaction_transfer.yaml diff --git a/planetmint/common/schema/transaction_v2.0.yaml b/planetmint/transactions/common/schema/v2.0/transaction.yaml similarity index 100% rename from planetmint/common/schema/transaction_v2.0.yaml rename to planetmint/transactions/common/schema/v2.0/transaction.yaml diff --git a/planetmint/common/schema/transaction_chain_migration_election_v2.0.yaml b/planetmint/transactions/common/schema/v2.0/transaction_chain_migration_election.yaml similarity index 100% rename from planetmint/common/schema/transaction_chain_migration_election_v2.0.yaml rename to planetmint/transactions/common/schema/v2.0/transaction_chain_migration_election.yaml diff --git a/planetmint/common/schema/transaction_create_v2.0.yaml b/planetmint/transactions/common/schema/v2.0/transaction_create.yaml similarity index 100% rename from planetmint/common/schema/transaction_create_v2.0.yaml rename to planetmint/transactions/common/schema/v2.0/transaction_create.yaml diff --git a/planetmint/common/schema/transaction_transfer_v2.0.yaml b/planetmint/transactions/common/schema/v2.0/transaction_transfer.yaml similarity index 100% rename from planetmint/common/schema/transaction_transfer_v2.0.yaml rename to planetmint/transactions/common/schema/v2.0/transaction_transfer.yaml diff --git a/planetmint/common/schema/transaction_validator_election_v2.0.yaml b/planetmint/transactions/common/schema/v2.0/transaction_validator_election.yaml similarity index 100% rename from planetmint/common/schema/transaction_validator_election_v2.0.yaml rename to planetmint/transactions/common/schema/v2.0/transaction_validator_election.yaml diff --git a/planetmint/common/schema/transaction_vote_v2.0.yaml b/planetmint/transactions/common/schema/v2.0/transaction_vote.yaml similarity index 100% rename from planetmint/common/schema/transaction_vote_v2.0.yaml rename to planetmint/transactions/common/schema/v2.0/transaction_vote.yaml diff --git a/planetmint/transactions/common/schema/v3.0/transaction.yaml b/planetmint/transactions/common/schema/v3.0/transaction.yaml new file mode 100644 index 0000000..ca64ce9 --- /dev/null +++ b/planetmint/transactions/common/schema/v3.0/transaction.yaml @@ -0,0 +1,174 @@ +# Copyright © 2020 Interplanetary Database Association e.V., +# Planetmint and IPDB software contributors. +# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +# Code is Apache-2.0 and docs are CC-BY-4.0 + +--- +"$schema": "http://json-schema.org/draft-04/schema#" +type: object +additionalProperties: false +title: Transaction Schema +required: +- id +- inputs +- outputs +- operation +- metadata +- assets +- version +properties: + id: + anyOf: + - "$ref": "#/definitions/sha3_hexdigest" + - type: 'null' + operation: + "$ref": "#/definitions/operation" + assets: + type: array + items: + "$ref": "#/definitions/asset" + inputs: + type: array + title: "Transaction inputs" + items: + "$ref": "#/definitions/input" + outputs: + type: array + items: + "$ref": "#/definitions/output" + metadata: + "$ref": "#/definitions/metadata" + version: + type: string + pattern: "^2\\.0$" +definitions: + offset: + type: integer + minimum: 0 + base58: + pattern: "[1-9a-zA-Z^OIl]{43,44}" + type: string + public_keys: + anyOf: + - type: array + items: + "$ref": "#/definitions/base58" + - type: 'null' + sha3_hexdigest: + pattern: "[0-9a-f]{64}" + type: string + uuid4: + pattern: "[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}" + type: string + operation: + type: string + enum: + - CREATE + - TRANSFER + - VALIDATOR_ELECTION + - CHAIN_MIGRATION_ELECTION + - VOTE + - COMPOSE + - DECOMPOSE + asset: + type: object + additionalProperties: false + properties: + id: + "$ref": "#/definitions/sha3_hexdigest" + data: + anyOf: + - type: object + additionalProperties: true + - type: 'null' + output: + type: object + additionalProperties: false + required: + - amount + - condition + - public_keys + properties: + amount: + type: string + pattern: "^[0-9]{1,20}$" + condition: + type: object + additionalProperties: false + required: + - details + - uri + properties: + details: + "$ref": "#/definitions/condition_details" + uri: + type: string + pattern: "^ni:///sha-256;([a-zA-Z0-9_-]{0,86})[?]\ + (fpt=(ed25519|threshold)-sha-256(&)?|cost=[0-9]+(&)?|\ + subtypes=ed25519-sha-256(&)?){2,3}$" + public_keys: + "$ref": "#/definitions/public_keys" + input: + type: "object" + additionalProperties: false + required: + - owners_before + - fulfillment + properties: + owners_before: + "$ref": "#/definitions/public_keys" + fulfillment: + anyOf: + - type: string + pattern: "^[a-zA-Z0-9_-]*$" + - "$ref": "#/definitions/condition_details" + fulfills: + anyOf: + - type: 'object' + additionalProperties: false + required: + - output_index + - transaction_id + properties: + output_index: + "$ref": "#/definitions/offset" + transaction_id: + "$ref": "#/definitions/sha3_hexdigest" + - type: 'null' + metadata: + anyOf: + - type: object + additionalProperties: true + minProperties: 1 + - type: 'null' + condition_details: + anyOf: + - type: object + additionalProperties: false + required: + - type + - public_key + properties: + type: + type: string + pattern: "^ed25519-sha-256$" + public_key: + "$ref": "#/definitions/base58" + - type: object + additionalProperties: false + required: + - type + - threshold + - subconditions + properties: + type: + type: "string" + pattern: "^threshold-sha-256$" + threshold: + type: integer + minimum: 1 + maximum: 100 + subconditions: + type: array + items: + "$ref": "#/definitions/condition_details" diff --git a/planetmint/transactions/common/schema/v3.0/transaction_chain_migration_election.yaml b/planetmint/transactions/common/schema/v3.0/transaction_chain_migration_election.yaml new file mode 100644 index 0000000..932c7b1 --- /dev/null +++ b/planetmint/transactions/common/schema/v3.0/transaction_chain_migration_election.yaml @@ -0,0 +1,51 @@ +# Copyright © 2020 Interplanetary Database Association e.V., +# Planetmint and IPDB software contributors. +# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +# Code is Apache-2.0 and docs are CC-BY-4.0 + +--- +"$schema": "http://json-schema.org/draft-04/schema#" +type: object +title: Chain Migration Election Schema - Propose a halt in block production to allow for a version change +required: +- operation +- assets +- outputs +properties: + operation: + type: string + value: "CHAIN_MIGRATION_ELECTION" + assets: + type: array + minItems: 1 + maxItems: 1 + items: + "$ref": "#/definitions/asset" + outputs: + type: array + items: + "$ref": "#/definitions/output" +definitions: + asset: + additionalProperties: false + properties: + data: + additionalProperties: false + properties: + seed: + type: string + required: + - data + output: + type: object + properties: + condition: + type: object + required: + - uri + properties: + uri: + type: string + pattern: "^ni:///sha-256;([a-zA-Z0-9_-]{0,86})[?]\ + (fpt=ed25519-sha-256(&)?|cost=[0-9]+(&)?|\ + subtypes=ed25519-sha-256(&)?){2,3}$" diff --git a/planetmint/transactions/common/schema/v3.0/transaction_create.yaml b/planetmint/transactions/common/schema/v3.0/transaction_create.yaml new file mode 100644 index 0000000..3a34a46 --- /dev/null +++ b/planetmint/transactions/common/schema/v3.0/transaction_create.yaml @@ -0,0 +1,42 @@ +# Copyright © 2020 Interplanetary Database Association e.V., +# Planetmint and IPDB software contributors. +# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +# Code is Apache-2.0 and docs are CC-BY-4.0 + +--- +"$schema": "http://json-schema.org/draft-04/schema#" +type: object +title: Transaction Schema - CREATE specific constraints +required: +- assets +- inputs +properties: + assets: + type: array + minItems: 1 + maxItems: 1 + items: + "$ref": "#/definitions/asset" + inputs: + type: array + title: "Transaction inputs" + maxItems: 1 + minItems: 1 + items: + type: "object" + required: + - fulfills + properties: + fulfills: + type: "null" +definitions: + asset: + additionalProperties: false + properties: + data: + anyOf: + - type: object + additionalProperties: true + - type: 'null' + required: + - data \ No newline at end of file diff --git a/planetmint/transactions/common/schema/v3.0/transaction_transfer.yaml b/planetmint/transactions/common/schema/v3.0/transaction_transfer.yaml new file mode 100644 index 0000000..1bc74e5 --- /dev/null +++ b/planetmint/transactions/common/schema/v3.0/transaction_transfer.yaml @@ -0,0 +1,39 @@ +# Copyright © 2020 Interplanetary Database Association e.V., +# Planetmint and IPDB software contributors. +# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +# Code is Apache-2.0 and docs are CC-BY-4.0 + +--- +"$schema": "http://json-schema.org/draft-04/schema#" +type: object +title: Transaction Schema - TRANSFER specific properties +required: +- assets +properties: + assets: + type: array + minItems: 1 + items: + "$ref": "#/definitions/asset" + inputs: + type: array + title: "Transaction inputs" + minItems: 1 + items: + type: "object" + required: + - fulfills + properties: + fulfills: + type: "object" +definitions: + sha3_hexdigest: + pattern: "[0-9a-f]{64}" + type: string + asset: + additionalProperties: false + properties: + id: + "$ref": "#/definitions/sha3_hexdigest" + required: + - id diff --git a/planetmint/transactions/common/schema/v3.0/transaction_validator_election.yaml b/planetmint/transactions/common/schema/v3.0/transaction_validator_election.yaml new file mode 100644 index 0000000..0d7c93b --- /dev/null +++ b/planetmint/transactions/common/schema/v3.0/transaction_validator_election.yaml @@ -0,0 +1,74 @@ +# Copyright © 2020 Interplanetary Database Association e.V., +# Planetmint and IPDB software contributors. +# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +# Code is Apache-2.0 and docs are CC-BY-4.0 + +--- +"$schema": "http://json-schema.org/draft-04/schema#" +type: object +title: Validator Election Schema - Propose a change to validator set +required: +- operation +- assets +- outputs +properties: + operation: + type: string + value: "VALIDATOR_ELECTION" + assets: + type: array + minItems: 1 + maxItems: 1 + items: + "$ref": "#/definitions/asset" + outputs: + type: array + items: + "$ref": "#/definitions/output" +definitions: + output: + type: object + properties: + condition: + type: object + required: + - uri + properties: + uri: + type: string + pattern: "^ni:///sha-256;([a-zA-Z0-9_-]{0,86})[?]\ + (fpt=ed25519-sha-256(&)?|cost=[0-9]+(&)?|\ + subtypes=ed25519-sha-256(&)?){2,3}$" + asset: + additionalProperties: false + properties: + data: + additionalProperties: false + properties: + node_id: + type: string + seed: + type: string + public_key: + type: object + additionalProperties: false + required: + - value + - type + properties: + value: + type: string + type: + type: string + enum: + - ed25519-base16 + - ed25519-base32 + - ed25519-base64 + power: + "$ref": "#/definitions/positiveInteger" + required: + - node_id + - public_key + - power + required: + - data diff --git a/planetmint/transactions/common/schema/v3.0/transaction_vote.yaml b/planetmint/transactions/common/schema/v3.0/transaction_vote.yaml new file mode 100644 index 0000000..64ed6ee --- /dev/null +++ b/planetmint/transactions/common/schema/v3.0/transaction_vote.yaml @@ -0,0 +1,34 @@ +# Copyright © 2020 Interplanetary Database Association e.V., +# Planetmint and IPDB software contributors. +# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +# Code is Apache-2.0 and docs are CC-BY-4.0 + +--- +"$schema": "http://json-schema.org/draft-04/schema#" +type: object +title: Vote Schema - Vote on an election +required: +- operation +- outputs +properties: + operation: + type: string + value: "VOTE" + outputs: + type: array + items: + "$ref": "#/definitions/output" +definitions: + output: + type: object + properties: + condition: + type: object + required: + - uri + properties: + uri: + type: string + pattern: "^ni:///sha-256;([a-zA-Z0-9_-]{0,86})[?]\ + (fpt=ed25519-sha-256(&)?|cost=[0-9]+(&)?|\ + subtypes=ed25519-sha-256(&)?){2,3}$" diff --git a/planetmint/common/transaction.py b/planetmint/transactions/common/transaction.py similarity index 55% rename from planetmint/common/transaction.py rename to planetmint/transactions/common/transaction.py index 1973b18..eaff144 100644 --- a/planetmint/common/transaction.py +++ b/planetmint/transactions/common/transaction.py @@ -13,7 +13,7 @@ Attributes: """ from collections import namedtuple from copy import deepcopy -from functools import reduce, lru_cache +from functools import lru_cache import rapidjson import base58 @@ -26,14 +26,15 @@ try: except ImportError: from sha3 import sha3_256 -from planetmint.common.crypto import PrivateKey, hash_data -from planetmint.common.exceptions import (KeypairMismatchException, - InputDoesNotExist, DoubleSpend, - InvalidHash, InvalidSignature, - AmountError, AssetIdMismatch, - ThresholdTooDeep) -from planetmint.common.utils import serialize +from planetmint.transactions.common.crypto import PrivateKey, hash_data +from planetmint.transactions.common.exceptions import ( + KeypairMismatchException, InputDoesNotExist, DoubleSpend, + InvalidHash, InvalidSignature, AmountError, AssetIdMismatch) +from planetmint.transactions.common.utils import serialize from .memoize import memoize_from_dict, memoize_to_dict +from .input import Input +from .output import Output +from .transaction_link import TransactionLink UnspentOutput = namedtuple( 'UnspentOutput', ( @@ -47,441 +48,6 @@ UnspentOutput = namedtuple( ) ) - -class Input(object): - """A Input is used to spend assets locked by an Output. - - Wraps around a Crypto-condition Fulfillment. - - Attributes: - fulfillment (:class:`cryptoconditions.Fulfillment`): A Fulfillment - to be signed with a private key. - owners_before (:obj:`list` of :obj:`str`): A list of owners after a - Transaction was confirmed. - fulfills (:class:`~planetmint.common.transaction. TransactionLink`, - optional): A link representing the input of a `TRANSFER` - Transaction. - """ - - def __init__(self, fulfillment, owners_before, fulfills=None): - """Create an instance of an :class:`~.Input`. - - Args: - fulfillment (:class:`cryptoconditions.Fulfillment`): A - Fulfillment to be signed with a private key. - owners_before (:obj:`list` of :obj:`str`): A list of owners - after a Transaction was confirmed. - fulfills (:class:`~planetmint.common.transaction. - TransactionLink`, optional): A link representing the input - of a `TRANSFER` Transaction. - """ - if fulfills is not None and not isinstance(fulfills, TransactionLink): - raise TypeError('`fulfills` must be a TransactionLink instance') - if not isinstance(owners_before, list): - raise TypeError('`owners_before` must be a list instance') - - self.fulfillment = fulfillment - self.fulfills = fulfills - self.owners_before = owners_before - - def __eq__(self, other): - # TODO: If `other !== Fulfillment` return `False` - return self.to_dict() == other.to_dict() - - # NOTE: This function is used to provide a unique key for a given - # Input to suppliment memoization - def __hash__(self): - return hash((self.fulfillment, self.fulfills)) - - def to_dict(self): - """Transforms the object to a Python dictionary. - - Note: - If an Input hasn't been signed yet, this method returns a - dictionary representation. - - Returns: - dict: The Input as an alternative serialization format. - """ - try: - fulfillment = self.fulfillment.serialize_uri() - except (TypeError, AttributeError, ASN1EncodeError, ASN1DecodeError): - fulfillment = _fulfillment_to_details(self.fulfillment) - - try: - # NOTE: `self.fulfills` can be `None` and that's fine - fulfills = self.fulfills.to_dict() - except AttributeError: - fulfills = None - - input_ = { - 'owners_before': self.owners_before, - 'fulfills': fulfills, - 'fulfillment': fulfillment, - } - return input_ - - @classmethod - def generate(cls, public_keys): - # TODO: write docstring - # The amount here does not really matter. It is only use on the - # output data model but here we only care about the fulfillment - output = Output.generate(public_keys, 1) - return cls(output.fulfillment, public_keys) - - @classmethod - def from_dict(cls, data): - """Transforms a Python dictionary to an Input object. - - Note: - Optionally, this method can also serialize a Cryptoconditions- - Fulfillment that is not yet signed. - - Args: - data (dict): The Input to be transformed. - - Returns: - :class:`~planetmint.common.transaction.Input` - - Raises: - InvalidSignature: If an Input's URI couldn't be parsed. - """ - fulfillment = data['fulfillment'] - if not isinstance(fulfillment, (Fulfillment, type(None))): - try: - fulfillment = Fulfillment.from_uri(data['fulfillment']) - except ASN1DecodeError: - # TODO Remove as it is legacy code, and simply fall back on - # ASN1DecodeError - raise InvalidSignature("Fulfillment URI couldn't been parsed") - except TypeError: - # NOTE: See comment about this special case in - # `Input.to_dict` - fulfillment = _fulfillment_from_details(data['fulfillment']) - fulfills = TransactionLink.from_dict(data['fulfills']) - return cls(fulfillment, data['owners_before'], fulfills) - - -def _fulfillment_to_details(fulfillment): - """Encode a fulfillment as a details dictionary - - Args: - fulfillment: Crypto-conditions Fulfillment object - """ - - if fulfillment.type_name == 'ed25519-sha-256': - return { - 'type': 'ed25519-sha-256', - 'public_key': base58.b58encode(fulfillment.public_key).decode(), - } - - if fulfillment.type_name == 'threshold-sha-256': - subconditions = [ - _fulfillment_to_details(cond['body']) - for cond in fulfillment.subconditions - ] - return { - 'type': 'threshold-sha-256', - 'threshold': fulfillment.threshold, - 'subconditions': subconditions, - } - - raise UnsupportedTypeError(fulfillment.type_name) - - -def _fulfillment_from_details(data, _depth=0): - """Load a fulfillment for a signing spec dictionary - - Args: - data: tx.output[].condition.details dictionary - """ - if _depth == 100: - raise ThresholdTooDeep() - - if data['type'] == 'ed25519-sha-256': - public_key = base58.b58decode(data['public_key']) - return Ed25519Sha256(public_key=public_key) - - if data['type'] == 'threshold-sha-256': - threshold = ThresholdSha256(data['threshold']) - for cond in data['subconditions']: - cond = _fulfillment_from_details(cond, _depth + 1) - threshold.add_subfulfillment(cond) - return threshold - - raise UnsupportedTypeError(data.get('type')) - - -class TransactionLink(object): - """An object for unidirectional linking to a Transaction's Output. - - Attributes: - txid (str, optional): A Transaction to link to. - output (int, optional): An output's index in a Transaction with id - `txid`. - """ - - def __init__(self, txid=None, output=None): - """Create an instance of a :class:`~.TransactionLink`. - - Note: - In an IPLD implementation, this class is not necessary anymore, - as an IPLD link can simply point to an object, as well as an - objects properties. So instead of having a (de)serializable - class, we can have a simple IPLD link of the form: - `//transaction/outputs//`. - - Args: - txid (str, optional): A Transaction to link to. - output (int, optional): An Outputs's index in a Transaction with - id `txid`. - """ - self.txid = txid - self.output = output - - def __bool__(self): - return self.txid is not None and self.output is not None - - def __eq__(self, other): - # TODO: If `other !== TransactionLink` return `False` - return self.to_dict() == other.to_dict() - - def __hash__(self): - return hash((self.txid, self.output)) - - @classmethod - def from_dict(cls, link): - """Transforms a Python dictionary to a TransactionLink object. - - Args: - link (dict): The link to be transformed. - - Returns: - :class:`~planetmint.common.transaction.TransactionLink` - """ - try: - return cls(link['transaction_id'], link['output_index']) - except TypeError: - return cls() - - def to_dict(self): - """Transforms the object to a Python dictionary. - - Returns: - (dict|None): The link as an alternative serialization format. - """ - if self.txid is None and self.output is None: - return None - else: - return { - 'transaction_id': self.txid, - 'output_index': self.output, - } - - def to_uri(self, path=''): - if self.txid is None and self.output is None: - return None - return '{}/transactions/{}/outputs/{}'.format(path, self.txid, - self.output) - - -class Output(object): - """An Output is used to lock an asset. - - Wraps around a Crypto-condition Condition. - - Attributes: - fulfillment (:class:`cryptoconditions.Fulfillment`): A Fulfillment - to extract a Condition from. - public_keys (:obj:`list` of :obj:`str`, optional): A list of - owners before a Transaction was confirmed. - """ - - MAX_AMOUNT = 9 * 10 ** 18 - - def __init__(self, fulfillment, public_keys=None, amount=1): - """Create an instance of a :class:`~.Output`. - - Args: - fulfillment (:class:`cryptoconditions.Fulfillment`): A - Fulfillment to extract a Condition from. - public_keys (:obj:`list` of :obj:`str`, optional): A list of - owners before a Transaction was confirmed. - amount (int): The amount of Assets to be locked with this - Output. - - Raises: - TypeError: if `public_keys` is not instance of `list`. - """ - if not isinstance(public_keys, list) and public_keys is not None: - raise TypeError('`public_keys` must be a list instance or None') - if not isinstance(amount, int): - raise TypeError('`amount` must be an int') - if amount < 1: - raise AmountError('`amount` must be greater than 0') - if amount > self.MAX_AMOUNT: - raise AmountError('`amount` must be <= %s' % self.MAX_AMOUNT) - - self.fulfillment = fulfillment - self.amount = amount - self.public_keys = public_keys - - def __eq__(self, other): - # TODO: If `other !== Condition` return `False` - return self.to_dict() == other.to_dict() - - def to_dict(self): - """Transforms the object to a Python dictionary. - - Note: - A dictionary serialization of the Input the Output was - derived from is always provided. - - Returns: - dict: The Output as an alternative serialization format. - """ - # TODO FOR CC: It must be able to recognize a hashlock condition - # and fulfillment! - condition = {} - try: - condition['details'] = _fulfillment_to_details(self.fulfillment) - except AttributeError: - pass - - try: - condition['uri'] = self.fulfillment.condition_uri - except AttributeError: - condition['uri'] = self.fulfillment - - output = { - 'public_keys': self.public_keys, - 'condition': condition, - 'amount': str(self.amount), - } - return output - - @classmethod - def generate(cls, public_keys, amount): - """Generates a Output from a specifically formed tuple or list. - - Note: - If a ThresholdCondition has to be generated where the threshold - is always the number of subconditions it is split between, a - list of the following structure is sufficient: - - [(address|condition)*, [(address|condition)*, ...], ...] - - Args: - public_keys (:obj:`list` of :obj:`str`): The public key of - the users that should be able to fulfill the Condition - that is being created. - amount (:obj:`int`): The amount locked by the Output. - - Returns: - An Output that can be used in a Transaction. - - Raises: - TypeError: If `public_keys` is not an instance of `list`. - ValueError: If `public_keys` is an empty list. - """ - threshold = len(public_keys) - if not isinstance(amount, int): - raise TypeError('`amount` must be a int') - if amount < 1: - raise AmountError('`amount` needs to be greater than zero') - if not isinstance(public_keys, list): - raise TypeError('`public_keys` must be an instance of list') - if len(public_keys) == 0: - raise ValueError('`public_keys` needs to contain at least one' - 'owner') - elif len(public_keys) == 1 and not isinstance(public_keys[0], list): - if isinstance(public_keys[0], Fulfillment): - ffill = public_keys[0] - else: - ffill = Ed25519Sha256( - public_key=base58.b58decode(public_keys[0])) - return cls(ffill, public_keys, amount=amount) - else: - initial_cond = ThresholdSha256(threshold=threshold) - threshold_cond = reduce(cls._gen_condition, public_keys, - initial_cond) - return cls(threshold_cond, public_keys, amount=amount) - - @classmethod - def _gen_condition(cls, initial, new_public_keys): - """Generates ThresholdSha256 conditions from a list of new owners. - - Note: - This method is intended only to be used with a reduce function. - For a description on how to use this method, see - :meth:`~.Output.generate`. - - Args: - initial (:class:`cryptoconditions.ThresholdSha256`): - A Condition representing the overall root. - new_public_keys (:obj:`list` of :obj:`str`|str): A list of new - owners or a single new owner. - - Returns: - :class:`cryptoconditions.ThresholdSha256`: - """ - try: - threshold = len(new_public_keys) - except TypeError: - threshold = None - - if isinstance(new_public_keys, list) and len(new_public_keys) > 1: - ffill = ThresholdSha256(threshold=threshold) - reduce(cls._gen_condition, new_public_keys, ffill) - elif isinstance(new_public_keys, list) and len(new_public_keys) <= 1: - raise ValueError('Sublist cannot contain single owner') - else: - try: - new_public_keys = new_public_keys.pop() - except AttributeError: - pass - # NOTE: Instead of submitting base58 encoded addresses, a user - # of this class can also submit fully instantiated - # Cryptoconditions. In the case of casting - # `new_public_keys` to a Ed25519Fulfillment with the - # result of a `TypeError`, we're assuming that - # `new_public_keys` is a Cryptocondition then. - if isinstance(new_public_keys, Fulfillment): - ffill = new_public_keys - else: - ffill = Ed25519Sha256( - public_key=base58.b58decode(new_public_keys)) - initial.add_subfulfillment(ffill) - return initial - - @classmethod - def from_dict(cls, data): - """Transforms a Python dictionary to an Output object. - - Note: - To pass a serialization cycle multiple times, a - Cryptoconditions Fulfillment needs to be present in the - passed-in dictionary, as Condition URIs are not serializable - anymore. - - Args: - data (dict): The dict to be transformed. - - Returns: - :class:`~planetmint.common.transaction.Output` - """ - try: - fulfillment = _fulfillment_from_details(data['condition']['details']) - except KeyError: - # NOTE: Hashlock condition case - fulfillment = data['condition']['uri'] - try: - amount = int(data['amount']) - except ValueError: - raise AmountError('Invalid amount: %s' % data['amount']) - return cls(fulfillment, data['public_keys'], amount) - - class Transaction(object): """A Transaction is used to create and transfer assets. @@ -491,10 +57,10 @@ class Transaction(object): Attributes: operation (str): Defines the operation of the Transaction. - inputs (:obj:`list` of :class:`~planetmint.common. + inputs (:obj:`list` of :class:`~planetmint.transactions.common. transaction.Input`, optional): Define the assets to spend. - outputs (:obj:`list` of :class:`~planetmint.common. + outputs (:obj:`list` of :class:`~planetmint.transactions.common. transaction.Output`, optional): Define the assets to lock. asset (dict): Asset payload for this Transaction. ``CREATE`` Transactions require a dict with a ``data`` @@ -521,9 +87,9 @@ class Transaction(object): Args: operation (str): Defines the operation of the Transaction. asset (dict): Asset payload for this Transaction. - inputs (:obj:`list` of :class:`~planetmint.common. + inputs (:obj:`list` of :class:`~planetmint.transactions.common. transaction.Input`, optional): Define the assets to - outputs (:obj:`list` of :class:`~planetmint.common. + outputs (:obj:`list` of :class:`~planetmint.transactions.common. transaction.Output`, optional): Define the assets to lock. metadata (dict): Metadata to be stored along with the @@ -602,137 +168,6 @@ class Transaction(object): def _hash(self): self._id = hash_data(self.serialized) - @classmethod - def validate_create(cls, tx_signers, recipients, asset, metadata): - if not isinstance(tx_signers, list): - raise TypeError('`tx_signers` must be a list instance') - if not isinstance(recipients, list): - raise TypeError('`recipients` must be a list instance') - if len(tx_signers) == 0: - raise ValueError('`tx_signers` list cannot be empty') - if len(recipients) == 0: - raise ValueError('`recipients` list cannot be empty') - if not (asset is None or isinstance(asset, dict)): - raise TypeError('`asset` must be a dict or None') - if not (metadata is None or isinstance(metadata, dict)): - raise TypeError('`metadata` must be a dict or None') - - inputs = [] - outputs = [] - - # generate_outputs - for recipient in recipients: - if not isinstance(recipient, tuple) or len(recipient) != 2: - raise ValueError(('Each `recipient` in the list must be a' - ' tuple of `([],' - ' )`')) - pub_keys, amount = recipient - outputs.append(Output.generate(pub_keys, amount)) - - # generate inputs - inputs.append(Input.generate(tx_signers)) - - return (inputs, outputs) - - @classmethod - def create(cls, tx_signers, recipients, metadata=None, asset=None): - """A simple way to generate a `CREATE` transaction. - - Note: - This method currently supports the following Cryptoconditions - use cases: - - Ed25519 - - ThresholdSha256 - - Additionally, it provides support for the following Planetmint - use cases: - - Multiple inputs and outputs. - - Args: - tx_signers (:obj:`list` of :obj:`str`): A list of keys that - represent the signers of the CREATE Transaction. - recipients (:obj:`list` of :obj:`tuple`): A list of - ([keys],amount) that represent the recipients of this - Transaction. - metadata (dict): The metadata to be stored along with the - Transaction. - asset (dict): The metadata associated with the asset that will - be created in this Transaction. - - Returns: - :class:`~planetmint.common.transaction.Transaction` - """ - - (inputs, outputs) = cls.validate_create(tx_signers, recipients, asset, metadata) - return cls(cls.CREATE, {'data': asset}, inputs, outputs, metadata) - - @classmethod - def validate_transfer(cls, inputs, recipients, asset_id, metadata): - if not isinstance(inputs, list): - raise TypeError('`inputs` must be a list instance') - if len(inputs) == 0: - raise ValueError('`inputs` must contain at least one item') - if not isinstance(recipients, list): - raise TypeError('`recipients` must be a list instance') - if len(recipients) == 0: - raise ValueError('`recipients` list cannot be empty') - - outputs = [] - for recipient in recipients: - if not isinstance(recipient, tuple) or len(recipient) != 2: - raise ValueError(('Each `recipient` in the list must be a' - ' tuple of `([],' - ' )`')) - pub_keys, amount = recipient - outputs.append(Output.generate(pub_keys, amount)) - - if not isinstance(asset_id, str): - raise TypeError('`asset_id` must be a string') - - return (deepcopy(inputs), outputs) - - @classmethod - def transfer(cls, inputs, recipients, asset_id, metadata=None): - """A simple way to generate a `TRANSFER` transaction. - - Note: - Different cases for threshold conditions: - - Combining multiple `inputs` with an arbitrary number of - `recipients` can yield interesting cases for the creation of - threshold conditions we'd like to support. The following - notation is proposed: - - 1. The index of a `recipient` corresponds to the index of - an input: - e.g. `transfer([input1], [a])`, means `input1` would now be - owned by user `a`. - - 2. `recipients` can (almost) get arbitrary deeply nested, - creating various complex threshold conditions: - e.g. `transfer([inp1, inp2], [[a, [b, c]], d])`, means - `a`'s signature would have a 50% weight on `inp1` - compared to `b` and `c` that share 25% of the leftover - weight respectively. `inp2` is owned completely by `d`. - - Args: - inputs (:obj:`list` of :class:`~planetmint.common.transaction. - Input`): Converted `Output`s, intended to - be used as inputs in the transfer to generate. - recipients (:obj:`list` of :obj:`tuple`): A list of - ([keys],amount) that represent the recipients of this - Transaction. - asset_id (str): The asset ID of the asset to be transferred in - this Transaction. - metadata (dict): Python dictionary to be stored along with the - Transaction. - - Returns: - :class:`~planetmint.common.transaction.Transaction` - """ - (inputs, outputs) = cls.validate_transfer(inputs, recipients, asset_id, metadata) - return cls(cls.TRANSFER, {'id': asset_id}, inputs, outputs, metadata) - def __eq__(self, other): try: other = other.to_dict() @@ -757,7 +192,7 @@ class Transaction(object): outputs should be returned as inputs. Returns: - :obj:`list` of :class:`~planetmint.common.transaction. + :obj:`list` of :class:`~planetmint.transactions.common.transaction. Input` """ # NOTE: If no indices are passed, we just assume to take all outputs @@ -774,7 +209,7 @@ class Transaction(object): """Adds an input to a Transaction's list of inputs. Args: - input_ (:class:`~planetmint.common.transaction. + input_ (:class:`~planetmint.transactions.common.transaction. Input`): An Input to be added to the Transaction. """ if not isinstance(input_, Input): @@ -785,7 +220,7 @@ class Transaction(object): """Adds an output to a Transaction's list of outputs. Args: - output (:class:`~planetmint.common.transaction. + output (:class:`~planetmint.transactions.common.transaction. Output`): An Output to be added to the Transaction. """ @@ -811,7 +246,7 @@ class Transaction(object): Transaction. Returns: - :class:`~planetmint.common.transaction.Transaction` + :class:`~planetmint.transactions.common.transaction.Transaction` """ # TODO: Singing should be possible with at least one of all private # keys supplied to this method. @@ -857,7 +292,7 @@ class Transaction(object): - ThresholdSha256. Args: - input_ (:class:`~planetmint.common.transaction. + input_ (:class:`~planetmint.transactions.common.transaction. Input`) The Input to be signed. message (str): The message to be signed key_pairs (dict): The keys to sign the Transaction with. @@ -878,7 +313,7 @@ class Transaction(object): """Signs a Ed25519Fulfillment. Args: - input_ (:class:`~planetmint.common.transaction. + input_ (:class:`~planetmint.transactions.common.transaction. Input`) The input to be signed. message (str): The message to be signed key_pairs (dict): The keys to sign the Transaction with. @@ -910,7 +345,7 @@ class Transaction(object): """Signs a ThresholdSha256. Args: - input_ (:class:`~planetmint.common.transaction. + input_ (:class:`~planetmint.transactions.common.transaction. Input`) The Input to be signed. message (str): The message to be signed key_pairs (dict): The keys to sign the Transaction with. @@ -962,7 +397,7 @@ class Transaction(object): evaluate parts of the validation-checks to `True`. Args: - outputs (:obj:`list` of :class:`~planetmint.common. + outputs (:obj:`list` of :class:`~planetmint.transactions.common. transaction.Output`): A list of Outputs to check the Inputs against. @@ -1025,7 +460,7 @@ class Transaction(object): does not validate against `output_condition_uri`. Args: - input_ (:class:`~planetmint.common.transaction. + input_ (:class:`~planetmint.transactions.common.transaction. Input`) The Input to be signed. operation (str): The type of Transaction. message (str): The fulfillment message. @@ -1135,7 +570,7 @@ class Transaction(object): transaction are related to the same asset id. Args: - transactions (:obj:`list` of :class:`~planetmint.common. + transactions (:obj:`list` of :class:`~planetmint.transactions.common. transaction.Transaction`): A list of Transactions. Usually input Transactions that should have a matching asset ID. @@ -1197,7 +632,7 @@ class Transaction(object): tx_body (dict): The Transaction to be transformed. Returns: - :class:`~planetmint.common.transaction.Transaction` + :class:`~planetmint.transactions.common.transaction.Transaction` """ operation = tx.get('operation', Transaction.CREATE) if isinstance(tx, dict) else Transaction.CREATE cls = Transaction.resolve_class(operation) diff --git a/planetmint/transactions/common/transaction_link.py b/planetmint/transactions/common/transaction_link.py new file mode 100644 index 0000000..fcdbeb1 --- /dev/null +++ b/planetmint/transactions/common/transaction_link.py @@ -0,0 +1,76 @@ +# Copyright © 2020 Interplanetary Database Association e.V., +# Planetmint and IPDB software contributors. +# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +# Code is Apache-2.0 and docs are CC-BY-4.0 + +class TransactionLink(object): + """An object for unidirectional linking to a Transaction's Output. + + Attributes: + txid (str, optional): A Transaction to link to. + output (int, optional): An output's index in a Transaction with id + `txid`. + """ + + def __init__(self, txid=None, output=None): + """Create an instance of a :class:`~.TransactionLink`. + + Note: + In an IPLD implementation, this class is not necessary anymore, + as an IPLD link can simply point to an object, as well as an + objects properties. So instead of having a (de)serializable + class, we can have a simple IPLD link of the form: + `//transaction/outputs//`. + + Args: + txid (str, optional): A Transaction to link to. + output (int, optional): An Outputs's index in a Transaction with + id `txid`. + """ + self.txid = txid + self.output = output + + def __bool__(self): + return self.txid is not None and self.output is not None + + def __eq__(self, other): + # TODO: If `other !== TransactionLink` return `False` + return self.to_dict() == other.to_dict() + + def __hash__(self): + return hash((self.txid, self.output)) + + @classmethod + def from_dict(cls, link): + """Transforms a Python dictionary to a TransactionLink object. + + Args: + link (dict): The link to be transformed. + + Returns: + :class:`~planetmint.transactions.common.transaction.TransactionLink` + """ + try: + return cls(link['transaction_id'], link['output_index']) + except TypeError: + return cls() + + def to_dict(self): + """Transforms the object to a Python dictionary. + + Returns: + (dict|None): The link as an alternative serialization format. + """ + if self.txid is None and self.output is None: + return None + else: + return { + 'transaction_id': self.txid, + 'output_index': self.output, + } + + def to_uri(self, path=''): + if self.txid is None and self.output is None: + return None + return '{}/transactions/{}/outputs/{}'.format(path, self.txid, + self.output) diff --git a/planetmint/common/transaction_mode_types.py b/planetmint/transactions/common/transaction_mode_types.py similarity index 100% rename from planetmint/common/transaction_mode_types.py rename to planetmint/transactions/common/transaction_mode_types.py diff --git a/planetmint/common/utils.py b/planetmint/transactions/common/utils.py similarity index 75% rename from planetmint/common/utils.py rename to planetmint/transactions/common/utils.py index 850117d..ed8090f 100644 --- a/planetmint/common/utils.py +++ b/planetmint/transactions/common/utils.py @@ -3,12 +3,16 @@ # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) # Code is Apache-2.0 and docs are CC-BY-4.0 +import base58 import time import re import rapidjson from planetmint.config import Config -from planetmint.common.exceptions import ValidationError +from planetmint.transactions.common.exceptions import ValidationError +from cryptoconditions import ThresholdSha256, Ed25519Sha256 +from planetmint.transactions.common.exceptions import ThresholdTooDeep +from cryptoconditions.exceptions import UnsupportedTypeError def gen_timestamp(): @@ -163,3 +167,52 @@ def validate_key(obj_name, key): 'key name cannot contain characters ' '".", "$" or null characters').format(key, obj_name) raise ValidationError(error_str) + +def _fulfillment_to_details(fulfillment): + """Encode a fulfillment as a details dictionary + + Args: + fulfillment: Crypto-conditions Fulfillment object + """ + + if fulfillment.type_name == 'ed25519-sha-256': + return { + 'type': 'ed25519-sha-256', + 'public_key': base58.b58encode(fulfillment.public_key).decode(), + } + + if fulfillment.type_name == 'threshold-sha-256': + subconditions = [ + _fulfillment_to_details(cond['body']) + for cond in fulfillment.subconditions + ] + return { + 'type': 'threshold-sha-256', + 'threshold': fulfillment.threshold, + 'subconditions': subconditions, + } + + raise UnsupportedTypeError(fulfillment.type_name) + + +def _fulfillment_from_details(data, _depth=0): + """Load a fulfillment for a signing spec dictionary + + Args: + data: tx.output[].condition.details dictionary + """ + if _depth == 100: + raise ThresholdTooDeep() + + if data['type'] == 'ed25519-sha-256': + public_key = base58.b58decode(data['public_key']) + return Ed25519Sha256(public_key=public_key) + + if data['type'] == 'threshold-sha-256': + threshold = ThresholdSha256(data['threshold']) + for cond in data['subconditions']: + cond = _fulfillment_from_details(cond, _depth + 1) + threshold.add_subfulfillment(cond) + return threshold + + raise UnsupportedTypeError(data.get('type')) diff --git a/planetmint/transactions/types/__init__.py b/planetmint/transactions/types/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/planetmint/transactions/types/assets/__init__.py b/planetmint/transactions/types/assets/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/planetmint/transactions/types/assets/create.py b/planetmint/transactions/types/assets/create.py new file mode 100644 index 0000000..3a38783 --- /dev/null +++ b/planetmint/transactions/types/assets/create.py @@ -0,0 +1,77 @@ +# Copyright © 2020 Interplanetary Database Association e.V., +# Planetmint and IPDB software contributors. +# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +# Code is Apache-2.0 and docs are CC-BY-4.0 + +from planetmint.models import Transaction +from planetmint.transactions.common.input import Input +from planetmint.transactions.common.output import Output + +class Create(Transaction): + + OPERATION = 'CREATE' + ALLOWED_OPERATIONS = (OPERATION,) + + @classmethod + def validate_create(self, tx_signers, recipients, asset, metadata): + if not isinstance(tx_signers, list): + raise TypeError('`tx_signers` must be a list instance') + if not isinstance(recipients, list): + raise TypeError('`recipients` must be a list instance') + if len(tx_signers) == 0: + raise ValueError('`tx_signers` list cannot be empty') + if len(recipients) == 0: + raise ValueError('`recipients` list cannot be empty') + if not (asset is None or isinstance(asset, dict)): + raise TypeError('`asset` must be a dict or None') + if not (metadata is None or isinstance(metadata, dict)): + raise TypeError('`metadata` must be a dict or None') + + inputs = [] + outputs = [] + + # generate_outputs + for recipient in recipients: + if not isinstance(recipient, tuple) or len(recipient) != 2: + raise ValueError(('Each `recipient` in the list must be a' + ' tuple of `([],' + ' )`')) + pub_keys, amount = recipient + outputs.append(Output.generate(pub_keys, amount)) + + # generate inputs + inputs.append(Input.generate(tx_signers)) + + return (inputs, outputs) + + @classmethod + def generate(cls, tx_signers, recipients, metadata=None, asset=None): + """A simple way to generate a `CREATE` transaction. + + Note: + This method currently supports the following Cryptoconditions + use cases: + - Ed25519 + - ThresholdSha256 + + Additionally, it provides support for the following Planetmint + use cases: + - Multiple inputs and outputs. + + Args: + tx_signers (:obj:`list` of :obj:`str`): A list of keys that + represent the signers of the CREATE Transaction. + recipients (:obj:`list` of :obj:`tuple`): A list of + ([keys],amount) that represent the recipients of this + Transaction. + metadata (dict): The metadata to be stored along with the + Transaction. + asset (dict): The metadata associated with the asset that will + be created in this Transaction. + + Returns: + :class:`~planetmint.common.transaction.Transaction` + """ + + (inputs, outputs) = cls.validate_create(tx_signers, recipients, asset, metadata) + return cls(cls.OPERATION, {'data': asset}, inputs, outputs, metadata) diff --git a/planetmint/transactions/types/assets/transfer.py b/planetmint/transactions/types/assets/transfer.py new file mode 100644 index 0000000..a658bc0 --- /dev/null +++ b/planetmint/transactions/types/assets/transfer.py @@ -0,0 +1,80 @@ +# Copyright © 2020 Interplanetary Database Association e.V., +# Planetmint and IPDB software contributors. +# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +# Code is Apache-2.0 and docs are CC-BY-4.0 + +from planetmint.models import Transaction +from planetmint.transactions.common.output import Output +from copy import deepcopy + +class Transfer(Transaction): + + OPERATION = 'TRANSFER' + ALLOWED_OPERATIONS = (OPERATION,) + + @classmethod + def validate_transfer(cls, inputs, recipients, asset_id, metadata): + if not isinstance(inputs, list): + raise TypeError('`inputs` must be a list instance') + if len(inputs) == 0: + raise ValueError('`inputs` must contain at least one item') + if not isinstance(recipients, list): + raise TypeError('`recipients` must be a list instance') + if len(recipients) == 0: + raise ValueError('`recipients` list cannot be empty') + + outputs = [] + for recipient in recipients: + if not isinstance(recipient, tuple) or len(recipient) != 2: + raise ValueError(('Each `recipient` in the list must be a' + ' tuple of `([],' + ' )`')) + pub_keys, amount = recipient + outputs.append(Output.generate(pub_keys, amount)) + + if not isinstance(asset_id, str): + raise TypeError('`asset_id` must be a string') + + return (deepcopy(inputs), outputs) + + @classmethod + def generate(cls, inputs, recipients, asset_id, metadata=None): + """A simple way to generate a `TRANSFER` transaction. + + Note: + Different cases for threshold conditions: + + Combining multiple `inputs` with an arbitrary number of + `recipients` can yield interesting cases for the creation of + threshold conditions we'd like to support. The following + notation is proposed: + + 1. The index of a `recipient` corresponds to the index of + an input: + e.g. `transfer([input1], [a])`, means `input1` would now be + owned by user `a`. + + 2. `recipients` can (almost) get arbitrary deeply nested, + creating various complex threshold conditions: + e.g. `transfer([inp1, inp2], [[a, [b, c]], d])`, means + `a`'s signature would have a 50% weight on `inp1` + compared to `b` and `c` that share 25% of the leftover + weight respectively. `inp2` is owned completely by `d`. + + Args: + inputs (:obj:`list` of :class:`~planetmint.common.transaction. + Input`): Converted `Output`s, intended to + be used as inputs in the transfer to generate. + recipients (:obj:`list` of :obj:`tuple`): A list of + ([keys],amount) that represent the recipients of this + Transaction. + asset_id (str): The asset ID of the asset to be transferred in + this Transaction. + metadata (dict): Python dictionary to be stored along with the + Transaction. + + Returns: + :class:`~planetmint.common.transaction.Transaction` + """ + (inputs, outputs) = cls.validate_transfer(inputs, recipients, asset_id, metadata) + return cls(cls.OPERATION, {'id': asset_id}, inputs, outputs, metadata) diff --git a/planetmint/transactions/types/elections/__init__.py b/planetmint/transactions/types/elections/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/planetmint/elections/election.py b/planetmint/transactions/types/elections/election.py similarity index 93% rename from planetmint/elections/election.py rename to planetmint/transactions/types/elections/election.py index d73b319..984d179 100644 --- a/planetmint/elections/election.py +++ b/planetmint/transactions/types/elections/election.py @@ -8,18 +8,17 @@ import base58 from uuid import uuid4 from planetmint import backend -from planetmint.elections.vote import Vote -from planetmint.common.exceptions import (InvalidSignature, - MultipleInputsError, - InvalidProposer, - UnequalValidatorSet, - DuplicateTransaction) +from planetmint.transactions.types.assets.create import Create +from planetmint.transactions.types.assets.transfer import Transfer +from planetmint.transactions.types.elections.vote import Vote +from planetmint.transactions.common.exceptions import ( + InvalidSignature, MultipleInputsError, InvalidProposer, + UnequalValidatorSet, DuplicateTransaction) from planetmint.tendermint_utils import key_from_base64, public_key_to_base64 -from planetmint.common.crypto import (public_key_from_ed25519_key) -from planetmint.common.transaction import Transaction -from planetmint.common.schema import (_validate_schema, - TX_SCHEMA_COMMON, - TX_SCHEMA_CREATE) +from planetmint.transactions.common.crypto import (public_key_from_ed25519_key) +from planetmint.transactions.common.transaction import Transaction +from planetmint.transactions.common.schema import ( + _validate_schema, TX_SCHEMA_COMMON, TX_SCHEMA_CREATE) class Election(Transaction): @@ -144,7 +143,7 @@ class Election(Transaction): uuid = uuid4() election_data['seed'] = str(uuid) - (inputs, outputs) = cls.validate_create(initiator, voters, election_data, metadata) + (inputs, outputs) = Create.validate_create(initiator, voters, election_data, metadata) election = cls(cls.OPERATION, {'data': election_data}, inputs, outputs, metadata) cls.validate_schema(election.to_dict()) return election @@ -161,11 +160,11 @@ class Election(Transaction): @classmethod def create(cls, tx_signers, recipients, metadata=None, asset=None): - raise NotImplementedError + Create.generate(tx_signers, recipients, metadata=None, asset=None) @classmethod def transfer(cls, tx_signers, recipients, metadata=None, asset=None): - raise NotImplementedError + Transfer.generate(tx_signers, recipients, metadata=None, asset=None) @classmethod def to_public_key(cls, election_id): diff --git a/planetmint/elections/vote.py b/planetmint/transactions/types/elections/vote.py similarity index 82% rename from planetmint/elections/vote.py rename to planetmint/transactions/types/elections/vote.py index a4bb0c9..c102a8c 100644 --- a/planetmint/elections/vote.py +++ b/planetmint/transactions/types/elections/vote.py @@ -3,14 +3,13 @@ # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) # Code is Apache-2.0 and docs are CC-BY-4.0 -from planetmint.common.transaction import Transaction -from planetmint.common.schema import (_validate_schema, - TX_SCHEMA_COMMON, - TX_SCHEMA_TRANSFER, - TX_SCHEMA_VOTE) +from planetmint.transactions.types.assets.create import Create +from planetmint.transactions.types.assets.transfer import Transfer +from planetmint.transactions.common.schema import ( + _validate_schema, TX_SCHEMA_COMMON, TX_SCHEMA_TRANSFER, TX_SCHEMA_VOTE) -class Vote(Transaction): +class Vote(Transfer): OPERATION = 'VOTE' # NOTE: This class inherits TRANSFER txn type. The `TRANSFER` property is @@ -57,8 +56,8 @@ class Vote(Transaction): @classmethod def create(cls, tx_signers, recipients, metadata=None, asset=None): - raise NotImplementedError + return Create.generate(tx_signers, recipients, metadata=None, asset=None) @classmethod def transfer(cls, tx_signers, recipients, metadata=None, asset=None): - raise NotImplementedError + return Transfer.generate(tx_signers, recipients, metadata=None, asset=None) diff --git a/planetmint/upsert_validator/validator_election.py b/planetmint/upsert_validator/validator_election.py index 5f26f92..31e4161 100644 --- a/planetmint/upsert_validator/validator_election.py +++ b/planetmint/upsert_validator/validator_election.py @@ -3,9 +3,9 @@ # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) # Code is Apache-2.0 and docs are CC-BY-4.0 -from planetmint.common.exceptions import InvalidPowerChange -from planetmint.elections.election import Election -from planetmint.common.schema import TX_SCHEMA_VALIDATOR_ELECTION +from planetmint.transactions.common.exceptions import InvalidPowerChange +from planetmint.transactions.types.elections.election import Election +from planetmint.transactions.common.schema import TX_SCHEMA_VALIDATOR_ELECTION from .validator_utils import (new_validator_set, encode_validator, validate_asset_public_key) diff --git a/planetmint/upsert_validator/validator_utils.py b/planetmint/upsert_validator/validator_utils.py index 36c03da..d1cf51c 100644 --- a/planetmint/upsert_validator/validator_utils.py +++ b/planetmint/upsert_validator/validator_utils.py @@ -2,33 +2,20 @@ import base64 import binascii import codecs - -from planetmint.config import Config -from abci import types_v0_22_8, types_v0_31_5, TmVersion -from planetmint.common.exceptions import InvalidPublicKey, BigchainDBError - +from tendermint.abci import types_pb2 +from tendermint.crypto import keys_pb2 +from planetmint.transactions.common.exceptions import InvalidPublicKey def encode_validator(v): ed25519_public_key = v['public_key']['value'] - # NOTE: tendermint expects public to be encoded in go-amino format - try: - version = TmVersion(Config().get()["tendermint"]["version"]) - except ValueError: - raise BigchainDBError('Invalid tendermint version, ' - 'check Planetmint configuration file') + pub_key = keys_pb2.PublicKey(ed25519=bytes.fromhex(ed25519_public_key)) - validator_update_t, pubkey_t = { - TmVersion.v0_22_8: (types_v0_22_8.Validator, types_v0_22_8.PubKey), - TmVersion.v0_31_5: (types_v0_31_5.ValidatorUpdate, types_v0_31_5.PubKey) - }[version] - pub_key = pubkey_t(type='ed25519', data=bytes.fromhex(ed25519_public_key)) - - return validator_update_t(pub_key=pub_key, power=v['power']) + return types_pb2.ValidatorUpdate(pub_key=pub_key, power=v['power']) def decode_validator(v): return {'public_key': {'type': 'ed25519-base64', - 'value': codecs.encode(v.pub_key.data, 'base64').decode().rstrip('\n')}, + 'value': codecs.encode(v.pub_key.ed25519, 'base64').decode().rstrip('\n')}, 'voting_power': v.power} diff --git a/planetmint/utils.py b/planetmint/utils.py index 51b0fa4..25dbc82 100644 --- a/planetmint/utils.py +++ b/planetmint/utils.py @@ -13,7 +13,7 @@ import setproctitle from packaging import version from planetmint.version import __tm_supported_versions__ from planetmint.tendermint_utils import key_from_base64 -from planetmint.common.crypto import key_pair_from_ed25519_key +from planetmint.transactions.common.crypto import key_pair_from_ed25519_key class ProcessGroup(object): diff --git a/planetmint/version.py b/planetmint/version.py index ec8d03e..3500cb5 100644 --- a/planetmint/version.py +++ b/planetmint/version.py @@ -3,8 +3,8 @@ # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) # Code is Apache-2.0 and docs are CC-BY-4.0 -__version__ = '0.9.0' +__version__ = '0.9.2' __short_version__ = '0.9' # Supported Tendermint versions -__tm_supported_versions__ = ["0.31.5", "0.22.8"] +__tm_supported_versions__ = ["0.34.15"] diff --git a/planetmint/web/routes.py b/planetmint/web/routes.py index 7bd51f0..2c650c0 100644 --- a/planetmint/web/routes.py +++ b/planetmint/web/routes.py @@ -34,6 +34,7 @@ ROUTES_API_V1 = [ r('assets/', assets.AssetListApi), r('metadata/', metadata.MetadataApi), r('blocks/', blocks.BlockApi), + r('blocks/latest', blocks.LatestBlock), r('blocks/', blocks.BlockListApi), r('transactions/', tx.TransactionApi), r('transactions', tx.TransactionListApi), diff --git a/planetmint/web/views/blocks.py b/planetmint/web/views/blocks.py index 5a8ae41..5154ba3 100644 --- a/planetmint/web/views/blocks.py +++ b/planetmint/web/views/blocks.py @@ -13,6 +13,25 @@ from flask_restful import Resource, reqparse from planetmint.web.views.base import make_error +class LatestBlock(Resource): + def get(self): + """API endpoint to get details about a block. + + Return: + A JSON string containing the data about the block. + """ + + pool = current_app.config['bigchain_pool'] + + with pool() as planet: + block = planet.get_latest_block() + + if not block: + return make_error(404) + + return block + + class BlockApi(Resource): def get(self, block_id): """API endpoint to get details about a block. diff --git a/planetmint/web/views/parameters.py b/planetmint/web/views/parameters.py index 530fc38..6df22ff 100644 --- a/planetmint/web/views/parameters.py +++ b/planetmint/web/views/parameters.py @@ -5,10 +5,8 @@ import re -from planetmint.common.transaction_mode_types import (BROADCAST_TX_COMMIT, - BROADCAST_TX_ASYNC, - BROADCAST_TX_SYNC) - +from planetmint.transactions.common.transaction_mode_types import ( + BROADCAST_TX_COMMIT, BROADCAST_TX_ASYNC, BROADCAST_TX_SYNC) def valid_txid(txid): if re.match('^[a-fA-F0-9]{64}$', txid): diff --git a/planetmint/web/views/transactions.py b/planetmint/web/views/transactions.py index c093ef7..eafaeed 100644 --- a/planetmint/web/views/transactions.py +++ b/planetmint/web/views/transactions.py @@ -12,8 +12,8 @@ import logging from flask import current_app, request, jsonify from flask_restful import Resource, reqparse -from planetmint.common.transaction_mode_types import BROADCAST_TX_ASYNC -from planetmint.common.exceptions import SchemaValidationError, ValidationError +from planetmint.transactions.common.transaction_mode_types import BROADCAST_TX_ASYNC +from planetmint.transactions.common.exceptions import SchemaValidationError, ValidationError from planetmint.web.views.base import make_error from planetmint.web.views import parameters from planetmint.models import Transaction diff --git a/pytest.ini b/pytest.ini index e0d356a..01b5ef6 100644 --- a/pytest.ini +++ b/pytest.ini @@ -2,8 +2,8 @@ testpaths = tests/ norecursedirs = .* *.egg *.egg-info env* devenv* docs addopts = -m "not abci" -#looponfail -#root = planetmint tests +looponfailroots = planetmint tests +asyncio_mode = strict markers = bdb: bdb skip: skip @@ -14,4 +14,5 @@ markers = web: web tendermint: tendermint execute: execute - + userfixtures + usefixture diff --git a/scripts/clean.sh b/scripts/clean.sh new file mode 100755 index 0000000..a047442 --- /dev/null +++ b/scripts/clean.sh @@ -0,0 +1,26 @@ +#!/bin/sh +# Copyright © 2020 Interplanetary Database Association e.V., +# Planetmint and IPDB software contributors. +# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +# Code is Apache-2.0 and docs are CC-BY-4.0 + +cd planetmint + +# Remove build artifacts +rm -fr build/ +rm -fr dist/ +rm -fr .eggs/ +find . -name '*.egg-info' -exec rm -fr {} + +find . -name '*.egg' -type f -exec rm -f {} + + +# Remove Python file artifacts +find . -name '*.pyc' -exec rm -f {} + +find . -name '*.pyo' -exec rm -f {} + +find . -name '*~' -exec rm -f {} + +find . -name '__pycache__' -exec rm -fr {} + + +# Remove test and coverage artifacts +find . -name '.pytest_cache' -exec rm -fr {} + +rm -fr .tox/ +rm -f .coverage +rm -fr htmlcov/ \ No newline at end of file diff --git a/scripts/election.sh b/scripts/election.sh new file mode 100755 index 0000000..0ff2d00 --- /dev/null +++ b/scripts/election.sh @@ -0,0 +1,76 @@ +#!/bin/bash +# Copyright © 2020 Interplanetary Database Association e.V., +# Planetmint and IPDB software contributors. +# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +# Code is Apache-2.0 and docs are CC-BY-4.0 + +# Change user and activate virtualenv +activate () { + cd /home/bigchaindb + source env/bigchaindb/bin/activate +} + +# Show tendermint node id +show_id () { + su tendermint -c "cd && go/bin/tendermint show_node_id" +} + +# Show validator public key +show_validator () { + su tendermint -c "cd && go/bin/tendermint show_validator" +} + +# Elect new voting power for node +elect_validator () { + activate + bigchaindb election new upsert-validator $1 $2 $3 --private-key /tmp/priv_validator_key.json +} + +# Show election state +show_election () { + activate + bigchaindb election show $1 +} + +# Approve election +approve_validator () { + activate + bigchaindb election approve $1 --private-key /tmp/priv_validator_key.json +} + +# Fetch tendermint id and pubkey and create upsert proposal +elect () { + node_id=$(show_id) + validator_pubkey=$(show_validator | jq -r .value) + proposal=$(elect_validator $validator_pubkey $1 $node_id 2>&1 | grep SUCCESS) + echo ${proposal##* } +} + +usage () { + echo "usage: TODO" +} + +while [ "$1" != "" ]; do + case $1 in + show_id ) show_id + ;; + show_validator ) show_validator + ;; + elect ) shift + elect $1 + ;; + show_election ) shift + show_election $1 + ;; + approve ) shift + approve_validator $1 + ;; + * ) usage + exit 1 + esac + shift +done + +exitcode=$? + +exit $exitcode \ No newline at end of file diff --git a/run-acceptance-test.sh b/scripts/run-acceptance-test.sh similarity index 100% rename from run-acceptance-test.sh rename to scripts/run-acceptance-test.sh diff --git a/scripts/run-integration-test.sh b/scripts/run-integration-test.sh new file mode 100755 index 0000000..1ec46d4 --- /dev/null +++ b/scripts/run-integration-test.sh @@ -0,0 +1,19 @@ +#!/bin/bash +# Copyright © 2020 Interplanetary Database Association e.V., +# Planetmint and IPDB software contributors. +# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +# Code is Apache-2.0 and docs are CC-BY-4.0 + +run_test() { + docker-compose -f docker-compose.integration.yml up test +} + +teardown () { + docker-compose -f docker-compose.integration.yml down +} + +run_test +exitcode=$? +teardown + +exit $exitcode \ No newline at end of file diff --git a/setup.py b/setup.py index 843086e..370b669 100644 --- a/setup.py +++ b/setup.py @@ -36,6 +36,15 @@ def check_setuptools_features(): ' $ pip3 install --upgrade setuptools\n' 'and then run this command again') +import pathlib +import pkg_resources + +with pathlib.Path('docs/root/requirements.txt').open() as requirements_txt: + docs_require= [ + str(requirement) + for requirement + in pkg_resources.parse_requirements(requirements_txt) + ] check_setuptools_features() @@ -44,17 +53,9 @@ dev_require = [ 'ipython', 'watchdog', 'logging_tree', - 'pre-commit' -] - -docs_require = [ - 'Sphinx~=1.0', - 'recommonmark>=0.4.0', - 'sphinx-rtd-theme>=0.1.9', - 'sphinxcontrib-httpdomain>=1.5.0', - 'sphinxcontrib-napoleon>=0.4.4', - 'aafigure>=0.6', - 'wget' + 'pre-commit', + 'twine', + 'ptvsd' ] tests_require = [ @@ -75,9 +76,9 @@ tests_require = [ install_requires = [ 'chardet==3.0.4', - 'aiohttp==3.7.4', - 'bigchaindb-abci==1.0.7', - 'planetmint-cryptoconditions>=0.9.0', + 'aiohttp==3.8.1', + 'abci==0.8.3', + 'planetmint-cryptoconditions>=0.9.4', 'flask-cors==3.0.10', 'flask-restful==0.3.9', 'flask==2.1.2', @@ -92,7 +93,7 @@ install_requires = [ 'pyyaml==5.4.1', 'requests==2.25.1', 'setproctitle==1.2.2', - 'ptvsd' + 'werkzeug==2.0.3' ] if sys.version_info < (3, 9): @@ -140,6 +141,8 @@ setup( 'dev': dev_require + tests_require + docs_require, 'docs': docs_require, }, - package_data={'planetmint.common.schema': ['*.yaml'], - 'planetmint.backend.tarantool': ['*.lua']} + package_data={ + 'planetmint.transactions.common.schema': ['v1.0/*.yaml','v2.0/*.yaml','v3.0/*.yaml' ], + 'planetmint.backend.tarantool': ['*.lua'], + }, ) diff --git a/tests/assets/test_digital_assets.py b/tests/assets/test_digital_assets.py index ba3f062..3a5b88b 100644 --- a/tests/assets/test_digital_assets.py +++ b/tests/assets/test_digital_assets.py @@ -5,12 +5,11 @@ import pytest import random - +from planetmint.transactions.types.assets.create import Create +from planetmint.transactions.types.assets.transfer import Transfer def test_asset_transfer(b, signed_create_tx, user_pk, user_sk): - from planetmint.models import Transaction - - tx_transfer = Transaction.transfer(signed_create_tx.to_inputs(), [([user_pk], 1)], + tx_transfer = Transfer.generate(signed_create_tx.to_inputs(), [([user_pk], 1)], signed_create_tx.id) tx_transfer_signed = tx_transfer.sign([user_sk]) @@ -21,10 +20,9 @@ def test_asset_transfer(b, signed_create_tx, user_pk, user_sk): def test_validate_transfer_asset_id_mismatch(b, signed_create_tx, user_pk, user_sk): - from planetmint.common.exceptions import AssetIdMismatch - from planetmint.models import Transaction + from planetmint.transactions.common.exceptions import AssetIdMismatch - tx_transfer = Transaction.transfer(signed_create_tx.to_inputs(), [([user_pk], 1)], + tx_transfer = Transfer.generate(signed_create_tx.to_inputs(), [([user_pk], 1)], signed_create_tx.id) tx_transfer.asset['id'] = 'a' * 64 tx_transfer_signed = tx_transfer.sign([user_sk]) @@ -37,14 +35,14 @@ def test_validate_transfer_asset_id_mismatch(b, signed_create_tx, user_pk, user_ def test_get_asset_id_create_transaction(alice, user_pk): from planetmint.models import Transaction - tx_create = Transaction.create([alice.public_key], [([user_pk], 1)]) + tx_create = Create.generate([alice.public_key], [([user_pk], 1)]) assert Transaction.get_asset_id(tx_create) == tx_create.id def test_get_asset_id_transfer_transaction(b, signed_create_tx, user_pk): from planetmint.models import Transaction - tx_transfer = Transaction.transfer(signed_create_tx.to_inputs(), [([user_pk], 1)], + tx_transfer = Transfer.generate(signed_create_tx.to_inputs(), [([user_pk], 1)], signed_create_tx.id) asset_id = Transaction.get_asset_id(tx_transfer) assert asset_id == tx_transfer.asset['id'] @@ -52,12 +50,12 @@ def test_get_asset_id_transfer_transaction(b, signed_create_tx, user_pk): def test_asset_id_mismatch(alice, user_pk): from planetmint.models import Transaction - from planetmint.common.exceptions import AssetIdMismatch + from planetmint.transactions.common.exceptions import AssetIdMismatch - tx1 = Transaction.create([alice.public_key], [([user_pk], 1)], + tx1 = Create.generate([alice.public_key], [([user_pk], 1)], metadata={'msg': random.random()}) tx1.sign([alice.private_key]) - tx2 = Transaction.create([alice.public_key], [([user_pk], 1)], + tx2 = Create.generate([alice.public_key], [([user_pk], 1)], metadata={'msg': random.random()}) tx2.sign([alice.private_key]) @@ -66,8 +64,7 @@ def test_asset_id_mismatch(alice, user_pk): def test_create_valid_divisible_asset(b, user_pk, user_sk): - from planetmint.models import Transaction - tx = Transaction.create([user_pk], [([user_pk], 2)]) + tx = Create.generate([user_pk], [([user_pk], 2)]) tx_signed = tx.sign([user_sk]) assert tx_signed.validate(b) == tx_signed diff --git a/tests/assets/test_divisible_assets.py b/tests/assets/test_divisible_assets.py index b700b4e..4c9ebd3 100644 --- a/tests/assets/test_divisible_assets.py +++ b/tests/assets/test_divisible_assets.py @@ -3,10 +3,13 @@ # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) # Code is Apache-2.0 and docs are CC-BY-4.0 + import pytest import random -from planetmint.common.exceptions import DoubleSpend +from planetmint.transactions.types.assets.create import Create +from planetmint.transactions.types.assets.transfer import Transfer +from planetmint.transactions.common.exceptions import DoubleSpend # CREATE divisible asset @@ -15,9 +18,8 @@ from planetmint.common.exceptions import DoubleSpend # Single output # Single owners_after def test_single_in_single_own_single_out_single_own_create(alice, user_pk, b): - from planetmint.models import Transaction - tx = Transaction.create([alice.public_key], [([user_pk], 100)], asset={'name': random.random()}) + tx = Create.generate([alice.public_key], [([user_pk], 100)], asset={'name': random.random()}) tx_signed = tx.sign([alice.private_key]) assert tx_signed.validate(b) == tx_signed @@ -32,9 +34,8 @@ def test_single_in_single_own_single_out_single_own_create(alice, user_pk, b): # Multiple outputs # Single owners_after per output def test_single_in_single_own_multiple_out_single_own_create(alice, user_pk, b): - from planetmint.models import Transaction - tx = Transaction.create([alice.public_key], [([user_pk], 50), ([user_pk], 50)], + tx = Create.generate([alice.public_key], [([user_pk], 50), ([user_pk], 50)], asset={'name': random.random()}) tx_signed = tx.sign([alice.private_key]) @@ -51,9 +52,8 @@ def test_single_in_single_own_multiple_out_single_own_create(alice, user_pk, b): # Single output # Multiple owners_after def test_single_in_single_own_single_out_multiple_own_create(alice, user_pk, b): - from planetmint.models import Transaction - tx = Transaction.create([alice.public_key], [([user_pk, user_pk], 100)], asset={'name': random.random()}) + tx = Create.generate([alice.public_key], [([user_pk, user_pk], 100)], asset={'name': random.random()}) tx_signed = tx.sign([alice.private_key]) assert tx_signed.validate(b) == tx_signed @@ -74,9 +74,8 @@ def test_single_in_single_own_single_out_multiple_own_create(alice, user_pk, b): # Mix: one output with a single owners_after, one output with multiple # owners_after def test_single_in_single_own_multiple_out_mix_own_create(alice, user_pk, b): - from planetmint.models import Transaction - tx = Transaction.create([alice.public_key], [([user_pk], 50), ([user_pk, user_pk], 50)], + tx = Create.generate([alice.public_key], [([user_pk], 50), ([user_pk, user_pk], 50)], asset={'name': random.random()}) tx_signed = tx.sign([alice.private_key]) @@ -98,10 +97,9 @@ def test_single_in_single_own_multiple_out_mix_own_create(alice, user_pk, b): # Output combinations already tested above def test_single_in_multiple_own_single_out_single_own_create(alice, b, user_pk, user_sk): - from planetmint.models import Transaction - from planetmint.common.transaction import _fulfillment_to_details + from planetmint.transactions.common.utils import _fulfillment_to_details - tx = Transaction.create([alice.public_key, user_pk], [([user_pk], 100)], asset={'name': random.random()}) + tx = Create.generate([alice.public_key, user_pk], [([user_pk], 100)], asset={'name': random.random()}) tx_signed = tx.sign([alice.private_key, user_sk]) assert tx_signed.validate(b) == tx_signed assert len(tx_signed.outputs) == 1 @@ -120,14 +118,13 @@ def test_single_in_multiple_own_single_out_single_own_create(alice, b, user_pk, # Single owners_after def test_single_in_single_own_single_out_single_own_transfer(alice, b, user_pk, user_sk): - from planetmint.models import Transaction # CREATE divisible asset - tx_create = Transaction.create([alice.public_key], [([user_pk], 100)], asset={'name': random.random()}) + tx_create = Create.generate([alice.public_key], [([user_pk], 100)], asset={'name': random.random()}) tx_create_signed = tx_create.sign([alice.private_key]) # TRANSFER - tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([alice.public_key], 100)], + tx_transfer = Transfer.generate(tx_create.to_inputs(), [([alice.public_key], 100)], asset_id=tx_create.id) tx_transfer_signed = tx_transfer.sign([user_sk]) @@ -146,14 +143,13 @@ def test_single_in_single_own_single_out_single_own_transfer(alice, b, user_pk, # Single owners_after def test_single_in_single_own_multiple_out_single_own_transfer(alice, b, user_pk, user_sk): - from planetmint.models import Transaction # CREATE divisible asset - tx_create = Transaction.create([alice.public_key], [([user_pk], 100)], asset={'name': random.random()}) + tx_create = Create.generate([alice.public_key], [([user_pk], 100)], asset={'name': random.random()}) tx_create_signed = tx_create.sign([alice.private_key]) # TRANSFER - tx_transfer = Transaction.transfer(tx_create.to_inputs(), + tx_transfer = Transfer.generate(tx_create.to_inputs(), [([alice.public_key], 50), ([alice.public_key], 50)], asset_id=tx_create.id) tx_transfer_signed = tx_transfer.sign([user_sk]) @@ -174,14 +170,13 @@ def test_single_in_single_own_multiple_out_single_own_transfer(alice, b, user_pk # Multiple owners_after def test_single_in_single_own_single_out_multiple_own_transfer(alice, b, user_pk, user_sk): - from planetmint.models import Transaction # CREATE divisible asset - tx_create = Transaction.create([alice.public_key], [([user_pk], 100)], asset={'name': random.random()}) + tx_create = Create.generate([alice.public_key], [([user_pk], 100)], asset={'name': random.random()}) tx_create_signed = tx_create.sign([alice.private_key]) # TRANSFER - tx_transfer = Transaction.transfer(tx_create.to_inputs(), + tx_transfer = Transfer.generate(tx_create.to_inputs(), [([alice.public_key, alice.public_key], 100)], asset_id=tx_create.id) tx_transfer_signed = tx_transfer.sign([user_sk]) @@ -210,14 +205,13 @@ def test_single_in_single_own_single_out_multiple_own_transfer(alice, b, user_pk # owners_after def test_single_in_single_own_multiple_out_mix_own_transfer(alice, b, user_pk, user_sk): - from planetmint.models import Transaction # CREATE divisible asset - tx_create = Transaction.create([alice.public_key], [([user_pk], 100)], asset={'name': random.random()}) + tx_create = Create.generate([alice.public_key], [([user_pk], 100)], asset={'name': random.random()}) tx_create_signed = tx_create.sign([alice.private_key]) # TRANSFER - tx_transfer = Transaction.transfer(tx_create.to_inputs(), + tx_transfer = Transfer.generate(tx_create.to_inputs(), [([alice.public_key], 50), ([alice.public_key, alice.public_key], 50)], asset_id=tx_create.id) tx_transfer_signed = tx_transfer.sign([user_sk]) @@ -247,16 +241,15 @@ def test_single_in_single_own_multiple_out_mix_own_transfer(alice, b, user_pk, # Single owners_after def test_single_in_multiple_own_single_out_single_own_transfer(alice, b, user_pk, user_sk): - from planetmint.models import Transaction - from planetmint.common.transaction import _fulfillment_to_details + from planetmint.transactions.common.utils import _fulfillment_to_details # CREATE divisible asset - tx_create = Transaction.create([alice.public_key], [([alice.public_key, user_pk], 100)], + tx_create = Create.generate([alice.public_key], [([alice.public_key, user_pk], 100)], asset={'name': random.random()}) tx_create_signed = tx_create.sign([alice.private_key]) # TRANSFER - tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([alice.public_key], 100)], + tx_transfer = Transfer.generate(tx_create.to_inputs(), [([alice.public_key], 100)], asset_id=tx_create.id) tx_transfer_signed = tx_transfer.sign([alice.private_key, user_sk]) @@ -283,15 +276,13 @@ def test_single_in_multiple_own_single_out_single_own_transfer(alice, b, user_pk # Single owners_after def test_multiple_in_single_own_single_out_single_own_transfer(alice, b, user_pk, user_sk): - from planetmint.models import Transaction - # CREATE divisible asset - tx_create = Transaction.create([alice.public_key], [([user_pk], 50), ([user_pk], 50)], + tx_create = Create.generate([alice.public_key], [([user_pk], 50), ([user_pk], 50)], asset={'name': random.random()}) tx_create_signed = tx_create.sign([alice.private_key]) # TRANSFER - tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([alice.public_key], 100)], + tx_transfer = Transfer.generate(tx_create.to_inputs(), [([alice.public_key], 100)], asset_id=tx_create.id) tx_transfer_signed = tx_transfer.sign([user_sk]) @@ -313,18 +304,17 @@ def test_multiple_in_single_own_single_out_single_own_transfer(alice, b, user_pk # Single output # Single owners_after def test_multiple_in_multiple_own_single_out_single_own_transfer(alice, b, user_pk, - user_sk): - from planetmint.models import Transaction - from planetmint.common.transaction import _fulfillment_to_details + user_sk): + from planetmint.transactions.common.utils import _fulfillment_to_details # CREATE divisible asset - tx_create = Transaction.create([alice.public_key], [([user_pk, alice.public_key], 50), - ([user_pk, alice.public_key], 50)], - asset={'name': random.random()}) + tx_create = Create.generate([alice.public_key], [([user_pk, alice.public_key], 50), + ([user_pk, alice.public_key], 50)], + asset={'name': random.random()}) tx_create_signed = tx_create.sign([alice.private_key]) # TRANSFER - tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([alice.public_key], 100)], + tx_transfer = Transfer.generate(tx_create.to_inputs(), [([alice.public_key], 100)], asset_id=tx_create.id) tx_transfer_signed = tx_transfer.sign([alice.private_key, user_sk]) @@ -355,16 +345,15 @@ def test_multiple_in_multiple_own_single_out_single_own_transfer(alice, b, user_ # Single owners_after def test_muiltiple_in_mix_own_multiple_out_single_own_transfer(alice, b, user_pk, user_sk): - from planetmint.models import Transaction - from planetmint.common.transaction import _fulfillment_to_details + from planetmint.transactions.common.utils import _fulfillment_to_details # CREATE divisible asset - tx_create = Transaction.create([alice.public_key], [([user_pk], 50), ([user_pk, alice.public_key], 50)], + tx_create = Create.generate([alice.public_key], [([user_pk], 50), ([user_pk, alice.public_key], 50)], asset={'name': random.random()}) tx_create_signed = tx_create.sign([alice.private_key]) # TRANSFER - tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([alice.public_key], 100)], + tx_transfer = Transfer.generate(tx_create.to_inputs(), [([alice.public_key], 100)], asset_id=tx_create.id) tx_transfer_signed = tx_transfer.sign([alice.private_key, user_sk]) @@ -395,15 +384,14 @@ def test_muiltiple_in_mix_own_multiple_out_single_own_transfer(alice, b, user_pk # owners_after def test_muiltiple_in_mix_own_multiple_out_mix_own_transfer(alice, b, user_pk, user_sk): - from planetmint.models import Transaction - from planetmint.common.transaction import _fulfillment_to_details + from planetmint.transactions.common.utils import _fulfillment_to_details # CREATE divisible asset - tx_create = Transaction.create([alice.public_key], [([user_pk], 50), ([user_pk, alice.public_key], 50)], + tx_create = Create.generate([alice.public_key], [([user_pk], 50), ([user_pk, alice.public_key], 50)], asset={'name': random.random()}) tx_create_signed = tx_create.sign([alice.private_key]) # TRANSFER - tx_transfer = Transaction.transfer(tx_create.to_inputs(), + tx_transfer = Transfer.generate(tx_create.to_inputs(), [([alice.public_key], 50), ([alice.public_key, user_pk], 50)], asset_id=tx_create.id) tx_transfer_signed = tx_transfer.sign([alice.private_key, user_sk]) @@ -438,12 +426,11 @@ def test_muiltiple_in_mix_own_multiple_out_mix_own_transfer(alice, b, user_pk, # Single output # Single owners_after def test_multiple_in_different_transactions(alice, b, user_pk, user_sk): - from planetmint.models import Transaction # CREATE divisible asset # `b` creates a divisible asset and assigns 50 shares to `b` and # 50 shares to `user_pk` - tx_create = Transaction.create([alice.public_key], [([user_pk], 50), ([alice.public_key], 50)], + tx_create = Create.generate([alice.public_key], [([user_pk], 50), ([alice.public_key], 50)], asset={'name': random.random()}) tx_create_signed = tx_create.sign([alice.private_key]) @@ -451,7 +438,7 @@ def test_multiple_in_different_transactions(alice, b, user_pk, user_sk): # `b` transfers its 50 shares to `user_pk` # after this transaction `user_pk` will have a total of 100 shares # split across two different transactions - tx_transfer1 = Transaction.transfer(tx_create.to_inputs([1]), + tx_transfer1 = Transfer.generate(tx_create.to_inputs([1]), [([user_pk], 50)], asset_id=tx_create.id) tx_transfer1_signed = tx_transfer1.sign([alice.private_key]) @@ -459,7 +446,7 @@ def test_multiple_in_different_transactions(alice, b, user_pk, user_sk): # TRANSFER # `user_pk` combines two different transaction with 50 shares each and # transfers a total of 100 shares back to `b` - tx_transfer2 = Transaction.transfer(tx_create.to_inputs([0]) + + tx_transfer2 = Transfer.generate(tx_create.to_inputs([0]) + tx_transfer1.to_inputs([0]), [([alice.private_key], 100)], asset_id=tx_create.id) @@ -482,18 +469,17 @@ def test_multiple_in_different_transactions(alice, b, user_pk, user_sk): # inputs needs to match the amount being sent in the outputs. # In other words `amount_in_inputs - amount_in_outputs == 0` def test_amount_error_transfer(alice, b, user_pk, user_sk): - from planetmint.models import Transaction - from planetmint.common.exceptions import AmountError + from planetmint.transactions.common.exceptions import AmountError # CREATE divisible asset - tx_create = Transaction.create([alice.public_key], [([user_pk], 100)], asset={'name': random.random()}) + tx_create = Create.generate([alice.public_key], [([user_pk], 100)], asset={'name': random.random()}) tx_create_signed = tx_create.sign([alice.private_key]) b.store_bulk_transactions([tx_create_signed]) # TRANSFER # output amount less than input amount - tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([alice.public_key], 50)], + tx_transfer = Transfer.generate(tx_create.to_inputs(), [([alice.public_key], 50)], asset_id=tx_create.id) tx_transfer_signed = tx_transfer.sign([user_sk]) @@ -502,7 +488,7 @@ def test_amount_error_transfer(alice, b, user_pk, user_sk): # TRANSFER # output amount greater than input amount - tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([alice.public_key], 101)], + tx_transfer = Transfer.generate(tx_create.to_inputs(), [([alice.public_key], 101)], asset_id=tx_create.id) tx_transfer_signed = tx_transfer.sign([user_sk]) @@ -518,15 +504,13 @@ def test_threshold_same_public_key(alice, b, user_pk, user_sk): # Creating threshold conditions with the same key does not make sense but # that does not mean that the code shouldn't work. - from planetmint.models import Transaction - # CREATE divisible asset - tx_create = Transaction.create([alice.public_key], [([user_pk, user_pk], 100)], + tx_create = Create.generate([alice.public_key], [([user_pk, user_pk], 100)], asset={'name': random.random()}) tx_create_signed = tx_create.sign([alice.private_key]) # TRANSFER - tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([alice.public_key], 100)], + tx_transfer = Transfer.generate(tx_create.to_inputs(), [([alice.public_key], 100)], asset_id=tx_create.id) tx_transfer_signed = tx_transfer.sign([user_sk, user_sk]) b.store_bulk_transactions([tx_create_signed]) @@ -539,16 +523,15 @@ def test_threshold_same_public_key(alice, b, user_pk, user_sk): def test_sum_amount(alice, b, user_pk, user_sk): - from planetmint.models import Transaction # CREATE divisible asset with 3 outputs with amount 1 - tx_create = Transaction.create([alice.public_key], [([user_pk], 1), ([user_pk], 1), ([user_pk], 1)], + tx_create = Create.generate([alice.public_key], [([user_pk], 1), ([user_pk], 1), ([user_pk], 1)], asset={'name': random.random()}) tx_create_signed = tx_create.sign([alice.private_key]) # create a transfer transaction with one output and check if the amount # is 3 - tx_transfer = Transaction.transfer(tx_create.to_inputs(), [([alice.public_key], 3)], + tx_transfer = Transfer.generate(tx_create.to_inputs(), [([alice.public_key], 3)], asset_id=tx_create.id) tx_transfer_signed = tx_transfer.sign([user_sk]) @@ -564,15 +547,14 @@ def test_sum_amount(alice, b, user_pk, user_sk): def test_divide(alice, b, user_pk, user_sk): - from planetmint.models import Transaction # CREATE divisible asset with 1 output with amount 3 - tx_create = Transaction.create([alice.public_key], [([user_pk], 3)], asset={'name': random.random()}) + tx_create = Create.generate([alice.public_key], [([user_pk], 3)], asset={'name': random.random()}) tx_create_signed = tx_create.sign([alice.private_key]) # create a transfer transaction with 3 outputs and check if the amount # of each output is 1 - tx_transfer = Transaction.transfer(tx_create.to_inputs(), + tx_transfer = Transfer.generate(tx_create.to_inputs(), [([alice.public_key], 1), ([alice.public_key], 1), ([alice.public_key], 1)], asset_id=tx_create.id) tx_transfer_signed = tx_transfer.sign([user_sk]) diff --git a/tests/backend/localmongodb/test_queries.py b/tests/backend/localmongodb/test_queries.py index ff65dd8..ac285fd 100644 --- a/tests/backend/localmongodb/test_queries.py +++ b/tests/backend/localmongodb/test_queries.py @@ -1,486 +1,484 @@ -# # Copyright © 2020 Interplanetary Database Association e.V., -# # Planetmint and IPDB software contributors. -# # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) -# # Code is Apache-2.0 and docs are CC-BY-4.0 - -# from copy import deepcopy - -# import pytest -# import pymongo - -# from planetmint.backend import Connection, query - - -# pytestmark = pytest.mark.bdb - - -# def test_get_txids_filtered(signed_create_tx, signed_transfer_tx): -# from planetmint.backend import Connection, query -# from planetmint.models import Transaction -# conn = Connection() # TODO First rewrite to get here tarantool connection -# print(conn) -# # create and insert two blocks, one for the create and one for the -# # transfer transactionTarantoolDBTarantoolDB -# conn.db.transactions.insert_one(signed_create_tx.to_dict()) -# conn.db.transactions.insert_one(signed_transfer_tx.to_dict()) - -# asset_id = Transaction.get_asset_id([signed_create_tx, signed_transfer_tx]) - -# # Test get by just asset id -# txids = set(query.get_txids_filtered(conn, asset_id)) -# assert txids == {signed_create_tx.id, signed_transfer_tx.id} - -# # Test get by asset and CREATE -# txids = set(query.get_txids_filtered(conn, asset_id, Transaction.CREATE)) -# assert txids == {signed_create_tx.id} - -# # Test get by asset and TRANSFER -# txids = set(query.get_txids_filtered(conn, asset_id, Transaction.TRANSFER)) -# assert txids == {signed_transfer_tx.id} - - -# def test_write_assets(): -# from planetmint.backend import Connection, query -# conn = Connection() - -# assets = [ -# {'id': 1, 'data': '1'}, -# {'id': 2, 'data': '2'}, -# {'id': 3, 'data': '3'}, -# # Duplicated id. Should not be written to the database -# {'id': 1, 'data': '1'}, -# ] - -# # write the assets -# for asset in assets: -# query.store_asset(conn, deepcopy(asset)) - -# # check that 3 assets were written to the database -# cursor = conn.db.assets.find({}, projection={'_id': False})\ -# .sort('id', pymongo.ASCENDING) - -# assert cursor.collection.count_documents({}) == 3 -# assert list(cursor) == assets[:-1] - - -# def test_get_assets(): -# from planetmint.backend import Connection, query -# conn = Connection() - -# assets = [ -# {'id': 1, 'data': '1'}, -# {'id': 2, 'data': '2'}, -# {'id': 3, 'data': '3'}, -# ] - -# conn.db.assets.insert_many(deepcopy(assets), ordered=False) - -# for asset in assets: -# assert query.get_asset(conn, asset['id']) - - -# @pytest.mark.parametrize('table', ['assets', 'metadata']) -# def test_text_search(table): -# from planetmint.backend import Connection, query -# conn = Connection() - -# # Example data and tests cases taken from the mongodb documentation -# # https://docs.mongodb.com/manual/reference/operator/query/text/ -# objects = [ -# {'id': 1, 'subject': 'coffee', 'author': 'xyz', 'views': 50}, -# {'id': 2, 'subject': 'Coffee Shopping', 'author': 'efg', 'views': 5}, -# {'id': 3, 'subject': 'Baking a cake', 'author': 'abc', 'views': 90}, -# {'id': 4, 'subject': 'baking', 'author': 'xyz', 'views': 100}, -# {'id': 5, 'subject': 'Café Con Leche', 'author': 'abc', 'views': 200}, -# {'id': 6, 'subject': 'Сырники', 'author': 'jkl', 'views': 80}, -# {'id': 7, 'subject': 'coffee and cream', 'author': 'efg', 'views': 10}, -# {'id': 8, 'subject': 'Cafe con Leche', 'author': 'xyz', 'views': 10} -# ] - -# # insert the assets -# conn.db[table].insert_many(deepcopy(objects), ordered=False) - -# # test search single word -# assert list(query.text_search(conn, 'coffee', table=table)) == [ -# {'id': 1, 'subject': 'coffee', 'author': 'xyz', 'views': 50}, -# {'id': 2, 'subject': 'Coffee Shopping', 'author': 'efg', 'views': 5}, -# {'id': 7, 'subject': 'coffee and cream', 'author': 'efg', 'views': 10}, -# ] - -# # match any of the search terms -# assert list(query.text_search(conn, 'bake coffee cake', table=table)) == [ -# {'author': 'abc', 'id': 3, 'subject': 'Baking a cake', 'views': 90}, -# {'author': 'xyz', 'id': 1, 'subject': 'coffee', 'views': 50}, -# {'author': 'xyz', 'id': 4, 'subject': 'baking', 'views': 100}, -# {'author': 'efg', 'id': 2, 'subject': 'Coffee Shopping', 'views': 5}, -# {'author': 'efg', 'id': 7, 'subject': 'coffee and cream', 'views': 10} -# ] - -# # search for a phrase -# assert list(query.text_search(conn, '\"coffee shop\"', table=table)) == [ -# {'id': 2, 'subject': 'Coffee Shopping', 'author': 'efg', 'views': 5}, -# ] - -# # exclude documents that contain a term -# assert list(query.text_search(conn, 'coffee -shop', table=table)) == [ -# {'id': 1, 'subject': 'coffee', 'author': 'xyz', 'views': 50}, -# {'id': 7, 'subject': 'coffee and cream', 'author': 'efg', 'views': 10}, -# ] - -# # search different language -# assert list(query.text_search(conn, 'leche', language='es', table=table)) == [ -# {'id': 5, 'subject': 'Café Con Leche', 'author': 'abc', 'views': 200}, -# {'id': 8, 'subject': 'Cafe con Leche', 'author': 'xyz', 'views': 10} -# ] - -# # case and diacritic insensitive search -# assert list(query.text_search(conn, 'сы́рники CAFÉS', table=table)) == [ -# {'id': 6, 'subject': 'Сырники', 'author': 'jkl', 'views': 80}, -# {'id': 5, 'subject': 'Café Con Leche', 'author': 'abc', 'views': 200}, -# {'id': 8, 'subject': 'Cafe con Leche', 'author': 'xyz', 'views': 10} -# ] - -# # case sensitive search -# assert list(query.text_search(conn, 'Coffee', case_sensitive=True, table=table)) == [ -# {'id': 2, 'subject': 'Coffee Shopping', 'author': 'efg', 'views': 5}, -# ] - -# # diacritic sensitive search -# assert list(query.text_search(conn, 'CAFÉ', diacritic_sensitive=True, table=table)) == [ -# {'id': 5, 'subject': 'Café Con Leche', 'author': 'abc', 'views': 200}, -# ] - -# # return text score -# assert list(query.text_search(conn, 'coffee', text_score=True, table=table)) == [ -# {'id': 1, 'subject': 'coffee', 'author': 'xyz', 'views': 50, 'score': 1.0}, -# {'id': 2, 'subject': 'Coffee Shopping', 'author': 'efg', 'views': 5, 'score': 0.75}, -# {'id': 7, 'subject': 'coffee and cream', 'author': 'efg', 'views': 10, 'score': 0.75}, -# ] - -# # limit search result -# assert list(query.text_search(conn, 'coffee', limit=2, table=table)) == [ -# {'id': 1, 'subject': 'coffee', 'author': 'xyz', 'views': 50}, -# {'id': 2, 'subject': 'Coffee Shopping', 'author': 'efg', 'views': 5}, -# ] - - -# def test_write_metadata(): -# from planetmint.backend import Connection, query -# conn = Connection() - -# metadata = [ -# {'id': 1, 'data': '1'}, -# {'id': 2, 'data': '2'}, -# {'id': 3, 'data': '3'} -# ] - -# # write the assets -# query.store_metadatas(conn, deepcopy(metadata)) - -# # check that 3 assets were written to the database -# cursor = conn.db.metadata.find({}, projection={'_id': False})\ -# .sort('id', pymongo.ASCENDING) -# assert cursor.collection.count_documents({}) == 3 -# assert list(cursor) == metadata - - -# def test_get_metadata(): -# from planetmint.backend import Connection, query -# conn = Connection() - -# metadata = [ -# {'id': 1, 'metadata': None}, -# {'id': 2, 'metadata': {'key': 'value'}}, -# {'id': 3, 'metadata': '3'}, -# ] - -# conn.db.metadata.insert_many(deepcopy(metadata), ordered=False) - -# for meta in metadata: -# assert query.get_metadata(conn, [meta['id']]) - - -# def test_get_owned_ids(signed_create_tx, user_pk): -# from planetmint.backend import Connection, query -# conn = Connection() - -# # insert a transaction -# conn.db.transactions.insert_one(deepcopy(signed_create_tx.to_dict())) - -# txns = list(query.get_owned_ids(conn, user_pk)) - -# assert txns[0] == signed_create_tx.to_dict() - - -# def test_get_spending_transactions(user_pk, user_sk): -# from planetmint.backend import Connection, query -# from planetmint.models import Transaction -# conn = Connection() - -# out = [([user_pk], 1)] -# tx1 = Transaction.create([user_pk], out * 3) -# tx1.sign([user_sk]) -# inputs = tx1.to_inputs() -# tx2 = Transaction.transfer([inputs[0]], out, tx1.id).sign([user_sk]) -# tx3 = Transaction.transfer([inputs[1]], out, tx1.id).sign([user_sk]) -# tx4 = Transaction.transfer([inputs[2]], out, tx1.id).sign([user_sk]) -# txns = [deepcopy(tx.to_dict()) for tx in [tx1, tx2, tx3, tx4]] -# conn.db.transactions.insert_many(txns) - -# links = [inputs[0].fulfills.to_dict(), inputs[2].fulfills.to_dict()] -# txns = list(query.get_spending_transactions(conn, links)) - -# # tx3 not a member because input 1 not asked for -# assert txns == [tx2.to_dict(), tx4.to_dict()] - - -# def test_get_spending_transactions_multiple_inputs(): -# from planetmint.backend import Connection, query -# from planetmint.models import Transaction -# from planetmint.common.crypto import generate_key_pair -# conn = Connection() -# (alice_sk, alice_pk) = generate_key_pair() -# (bob_sk, bob_pk) = generate_key_pair() -# (carol_sk, carol_pk) = generate_key_pair() - -# out = [([alice_pk], 9)] -# tx1 = Transaction.create([alice_pk], out).sign([alice_sk]) - -# inputs1 = tx1.to_inputs() -# tx2 = Transaction.transfer([inputs1[0]], -# [([alice_pk], 6), ([bob_pk], 3)], -# tx1.id).sign([alice_sk]) - -# inputs2 = tx2.to_inputs() -# tx3 = Transaction.transfer([inputs2[0]], -# [([bob_pk], 3), ([carol_pk], 3)], -# tx1.id).sign([alice_sk]) - -# inputs3 = tx3.to_inputs() -# tx4 = Transaction.transfer([inputs2[1], inputs3[0]], -# [([carol_pk], 6)], -# tx1.id).sign([bob_sk]) - -# txns = [deepcopy(tx.to_dict()) for tx in [tx1, tx2, tx3, tx4]] -# conn.db.transactions.insert_many(txns) - -# links = [ -# ({'transaction_id': tx2.id, 'output_index': 0}, 1, [tx3.id]), -# ({'transaction_id': tx2.id, 'output_index': 1}, 1, [tx4.id]), -# ({'transaction_id': tx3.id, 'output_index': 0}, 1, [tx4.id]), -# ({'transaction_id': tx3.id, 'output_index': 1}, 0, None), -# ] -# for li, num, match in links: -# txns = list(query.get_spending_transactions(conn, [li])) -# assert len(txns) == num -# if len(txns): -# assert [tx['id'] for tx in txns] == match - - -# def test_store_block(): -# from planetmint.backend import Connection, query -# from planetmint.lib import Block -# conn = Connection() - -# block = Block(app_hash='random_utxo', -# height=3, -# transactions=[]) -# query.store_block(conn, block._asdict()) -# cursor = conn.db.blocks.find({}, projection={'_id': False}) -# assert cursor.collection.count_documents({}) == 1 - - -# def test_get_block(): -# from planetmint.backend import Connection, query -# from planetmint.lib import Block -# conn = Connection() - -# block = Block(app_hash='random_utxo', -# height=3, -# transactions=[]) - -# conn.db.blocks.insert_one(block._asdict()) - -# block = dict(query.get_block(conn, 3)) -# assert block['height'] == 3 - - -# def test_delete_zero_unspent_outputs(db_context, utxoset): -# from planetmint.backend import query -# unspent_outputs, utxo_collection = utxoset -# delete_res = query.delete_unspent_outputs(db_context.conn) -# assert delete_res is None -# assert utxo_collection.count_documents({}) == 3 -# assert utxo_collection.count_documents( -# {'$or': [ -# {'transaction_id': 'a', 'output_index': 0}, -# {'transaction_id': 'b', 'output_index': 0}, -# {'transaction_id': 'a', 'output_index': 1}, -# ]} -# ) == 3 - - -# def test_delete_one_unspent_outputs(db_context, utxoset): -# from planetmint.backend import query -# unspent_outputs, utxo_collection = utxoset -# delete_res = query.delete_unspent_outputs(db_context.conn, -# unspent_outputs[0]) -# assert delete_res.raw_result['n'] == 1 -# assert utxo_collection.count_documents( -# {'$or': [ -# {'transaction_id': 'a', 'output_index': 1}, -# {'transaction_id': 'b', 'output_index': 0}, -# ]} -# ) == 2 -# assert utxo_collection.count_documents( -# {'transaction_id': 'a', 'output_index': 0}) == 0 - - -# def test_delete_many_unspent_outputs(db_context, utxoset): -# from planetmint.backend import query -# unspent_outputs, utxo_collection = utxoset -# delete_res = query.delete_unspent_outputs(db_context.conn, -# *unspent_outputs[::2]) -# assert delete_res.raw_result['n'] == 2 -# assert utxo_collection.count_documents( -# {'$or': [ -# {'transaction_id': 'a', 'output_index': 0}, -# {'transaction_id': 'b', 'output_index': 0}, -# ]} -# ) == 0 -# assert utxo_collection.count_documents( -# {'transaction_id': 'a', 'output_index': 1}) == 1 - - -# def test_store_zero_unspent_output(db_context, utxo_collection): -# from planetmint.backend import query -# res = query.store_unspent_outputs(db_context.conn) -# assert res is None -# assert utxo_collection.count_documents({}) == 0 - - -# def test_store_one_unspent_output(db_context, -# unspent_output_1, utxo_collection): -# from planetmint.backend import query -# res = query.store_unspent_outputs(db_context.conn, unspent_output_1) -# assert res.acknowledged -# assert len(res.inserted_ids) == 1 -# assert utxo_collection.count_documents( -# {'transaction_id': unspent_output_1['transaction_id'], -# 'output_index': unspent_output_1['output_index']} -# ) == 1 - - -# def test_store_many_unspent_outputs(db_context, -# unspent_outputs, utxo_collection): -# from planetmint.backend import query -# res = query.store_unspent_outputs(db_context.conn, *unspent_outputs) -# assert res.acknowledged -# assert len(res.inserted_ids) == 3 -# assert utxo_collection.count_documents( -# {'transaction_id': unspent_outputs[0]['transaction_id']} -# ) == 3 - - -# def test_get_unspent_outputs(db_context, utxoset): -# from planetmint.backend import query -# cursor = query.get_unspent_outputs(db_context.conn) -# assert cursor.collection.count_documents({}) == 3 -# retrieved_utxoset = list(cursor) -# unspent_outputs, utxo_collection = utxoset -# assert retrieved_utxoset == list( -# utxo_collection.find(projection={'_id': False})) -# assert retrieved_utxoset == unspent_outputs - - -# def test_store_pre_commit_state(db_context): -# from planetmint.backend import query - -# state = dict(height=3, transactions=[]) - -# query.store_pre_commit_state(db_context.conn, state) -# cursor = db_context.conn.db.pre_commit.find({'commit_id': 'test'}, -# projection={'_id': False}) -# assert cursor.collection.count_documents({}) == 1 - - -# def test_get_pre_commit_state(db_context): -# from planetmint.backend import query - -# state = dict(height=3, transactions=[]) -# db_context.conn.db.pre_commit.insert_one(state) -# resp = query.get_pre_commit_state(db_context.conn) -# assert resp == state - - -# def test_validator_update(): -# from planetmint.backend import Connection, query - -# conn = Connection() - -# def gen_validator_update(height): -# return {'data': 'somedata', 'height': height, 'election_id': f'election_id_at_height_{height}'} - -# for i in range(1, 100, 10): -# value = gen_validator_update(i) -# query.store_validator_set(conn, value) - -# v1 = query.get_validator_set(conn, 8) -# assert v1['height'] == 1 - -# v41 = query.get_validator_set(conn, 50) -# assert v41['height'] == 41 - -# v91 = query.get_validator_set(conn) -# assert v91['height'] == 91 - - -# @pytest.mark.parametrize('description,stores,expected', [ -# ( -# 'Query empty database.', -# [], -# None, -# ), -# ( -# 'Store one chain with the default value for `is_synced`.', -# [ -# {'height': 0, 'chain_id': 'some-id'}, -# ], -# {'height': 0, 'chain_id': 'some-id', 'is_synced': True}, -# ), -# ( -# 'Store one chain with a custom value for `is_synced`.', -# [ -# {'height': 0, 'chain_id': 'some-id', 'is_synced': False}, -# ], -# {'height': 0, 'chain_id': 'some-id', 'is_synced': False}, -# ), -# ( -# 'Store one chain, then update it.', -# [ -# {'height': 0, 'chain_id': 'some-id', 'is_synced': True}, -# {'height': 0, 'chain_id': 'new-id', 'is_synced': False}, -# ], -# {'height': 0, 'chain_id': 'new-id', 'is_synced': False}, -# ), -# ( -# 'Store a chain, update it, store another chain.', -# [ -# {'height': 0, 'chain_id': 'some-id', 'is_synced': True}, -# {'height': 0, 'chain_id': 'some-id', 'is_synced': False}, -# {'height': 10, 'chain_id': 'another-id', 'is_synced': True}, -# ], -# {'height': 10, 'chain_id': 'another-id', 'is_synced': True}, -# ), -# ]) -# def test_store_abci_chain(description, stores, expected): -# conn = Connection() - -# for store in stores: -# query.store_abci_chain(conn, **store) - -# actual = query.get_latest_abci_chain(conn) -# assert expected == actual, description - -# #test_get_txids_filtered(None, None) - +## # Copyright © 2020 Interplanetary Database Association e.V., +## # Planetmint and IPDB software contributors. +## # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) +## # Code is Apache-2.0 and docs are CC-BY-4.0 +# +#from copy import deepcopy +#from planetmint.transactions.types.assets.create import Create +#from planetmint.transactions.types.assets.transfer import Transfer +# +## import pytest +## import pymongo +# +## from planetmint.backend import Connection, query +# +# +## pytestmark = pytest.mark.bdb +# +#@pytest.mark.skip +#def test_get_txids_filtered(signed_create_tx, signed_transfer_tx): +# from planetmint.backend import connect, query +# from planetmint.models import Transaction +# conn = connect() +# +# # create and insert two blocks, one for the create and one for the +# # transfer transaction +# conn.db.transactions.insert_one(signed_create_tx.to_dict()) +# conn.db.transactions.insert_one(signed_transfer_tx.to_dict()) +# +# asset_id = Transaction.get_asset_id([signed_create_tx, signed_transfer_tx]) +# +# # Test get by just asset id +# txids = set(query.get_txids_filtered(conn, asset_id)) +# assert txids == {signed_create_tx.id, signed_transfer_tx.id} +# +# # Test get by asset and CREATE +# txids = set(query.get_txids_filtered(conn, asset_id, Transaction.CREATE)) +# assert txids == {signed_create_tx.id} +# +# # Test get by asset and TRANSFER +# txids = set(query.get_txids_filtered(conn, asset_id, Transaction.TRANSFER)) +# assert txids == {signed_transfer_tx.id} +# +#@pytest.mark.skip +#def test_write_assets(): +# from planetmint.backend import connect, query +# conn = connect() +# +# assets = [ +# {'id': 1, 'data': '1'}, +# {'id': 2, 'data': '2'}, +# {'id': 3, 'data': '3'}, +# # Duplicated id. Should not be written to the database +# {'id': 1, 'data': '1'}, +# ] +# +# # write the assets +# for asset in assets: +# query.store_asset(conn, deepcopy(asset)) +# +# # check that 3 assets were written to the database +# cursor = conn.db.assets.find({}, projection={'_id': False})\ +# .sort('id', pymongo.ASCENDING) +# +# assert cursor.collection.count_documents({}) == 3 +# assert list(cursor) == assets[:-1] +# +#@pytest.mark.skip +#def test_get_assets(): +# from planetmint.backend import connect, query +# conn = connect() +# +# assets = [ +# {'id': 1, 'data': '1'}, +# {'id': 2, 'data': '2'}, +# {'id': 3, 'data': '3'}, +# ] +# +# conn.db.assets.insert_many(deepcopy(assets), ordered=False) +# +# for asset in assets: +# assert query.get_asset(conn, asset['id']) +# +#@pytest.mark.skip +#@pytest.mark.parametrize('table', ['assets', 'metadata']) +#def test_text_search(table): +# from planetmint.backend import connect, query +# conn = connect() +# +# # Example data and tests cases taken from the mongodb documentation +# # https://docs.mongodb.com/manual/reference/operator/query/text/ +# objects = [ +# {'id': 1, 'subject': 'coffee', 'author': 'xyz', 'views': 50}, +# {'id': 2, 'subject': 'Coffee Shopping', 'author': 'efg', 'views': 5}, +# {'id': 3, 'subject': 'Baking a cake', 'author': 'abc', 'views': 90}, +# {'id': 4, 'subject': 'baking', 'author': 'xyz', 'views': 100}, +# {'id': 5, 'subject': 'Café Con Leche', 'author': 'abc', 'views': 200}, +# {'id': 6, 'subject': 'Сырники', 'author': 'jkl', 'views': 80}, +# {'id': 7, 'subject': 'coffee and cream', 'author': 'efg', 'views': 10}, +# {'id': 8, 'subject': 'Cafe con Leche', 'author': 'xyz', 'views': 10} +# ] +# +# # insert the assets +# conn.db[table].insert_many(deepcopy(objects), ordered=False) +# +# # test search single word +# assert list(query.text_search(conn, 'coffee', table=table)) == [ +# {'id': 1, 'subject': 'coffee', 'author': 'xyz', 'views': 50}, +# {'id': 2, 'subject': 'Coffee Shopping', 'author': 'efg', 'views': 5}, +# {'id': 7, 'subject': 'coffee and cream', 'author': 'efg', 'views': 10}, +# ] +# +# # match any of the search terms +# assert list(query.text_search(conn, 'bake coffee cake', table=table)) == [ +# {'author': 'abc', 'id': 3, 'subject': 'Baking a cake', 'views': 90}, +# {'author': 'xyz', 'id': 1, 'subject': 'coffee', 'views': 50}, +# {'author': 'xyz', 'id': 4, 'subject': 'baking', 'views': 100}, +# {'author': 'efg', 'id': 2, 'subject': 'Coffee Shopping', 'views': 5}, +# {'author': 'efg', 'id': 7, 'subject': 'coffee and cream', 'views': 10} +# ] +# +# # search for a phrase +# assert list(query.text_search(conn, '\"coffee shop\"', table=table)) == [ +# {'id': 2, 'subject': 'Coffee Shopping', 'author': 'efg', 'views': 5}, +# ] +# +# # exclude documents that contain a term +# assert list(query.text_search(conn, 'coffee -shop', table=table)) == [ +# {'id': 1, 'subject': 'coffee', 'author': 'xyz', 'views': 50}, +# {'id': 7, 'subject': 'coffee and cream', 'author': 'efg', 'views': 10}, +# ] +# +# # search different language +# assert list(query.text_search(conn, 'leche', language='es', table=table)) == [ +# {'id': 5, 'subject': 'Café Con Leche', 'author': 'abc', 'views': 200}, +# {'id': 8, 'subject': 'Cafe con Leche', 'author': 'xyz', 'views': 10} +# ] +# +# # case and diacritic insensitive search +# assert list(query.text_search(conn, 'сы́рники CAFÉS', table=table)) == [ +# {'id': 6, 'subject': 'Сырники', 'author': 'jkl', 'views': 80}, +# {'id': 5, 'subject': 'Café Con Leche', 'author': 'abc', 'views': 200}, +# {'id': 8, 'subject': 'Cafe con Leche', 'author': 'xyz', 'views': 10} +# ] +# +# # case sensitive search +# assert list(query.text_search(conn, 'Coffee', case_sensitive=True, table=table)) == [ +# {'id': 2, 'subject': 'Coffee Shopping', 'author': 'efg', 'views': 5}, +# ] +# +# # diacritic sensitive search +# assert list(query.text_search(conn, 'CAFÉ', diacritic_sensitive=True, table=table)) == [ +# {'id': 5, 'subject': 'Café Con Leche', 'author': 'abc', 'views': 200}, +# ] +# +# # return text score +# assert list(query.text_search(conn, 'coffee', text_score=True, table=table)) == [ +# {'id': 1, 'subject': 'coffee', 'author': 'xyz', 'views': 50, 'score': 1.0}, +# {'id': 2, 'subject': 'Coffee Shopping', 'author': 'efg', 'views': 5, 'score': 0.75}, +# {'id': 7, 'subject': 'coffee and cream', 'author': 'efg', 'views': 10, 'score': 0.75}, +# ] +# +# # limit search result +# assert list(query.text_search(conn, 'coffee', limit=2, table=table)) == [ +# {'id': 1, 'subject': 'coffee', 'author': 'xyz', 'views': 50}, +# {'id': 2, 'subject': 'Coffee Shopping', 'author': 'efg', 'views': 5}, +# ] +# +#@pytest.mark.skip +#def test_write_metadata(): +# from planetmint.backend import connect, query +# conn = connect() +# +# metadata = [ +# {'id': 1, 'data': '1'}, +# {'id': 2, 'data': '2'}, +# {'id': 3, 'data': '3'} +# ] +# +# # write the assets +# query.store_metadatas(conn, deepcopy(metadata)) +# +# # check that 3 assets were written to the database +# cursor = conn.db.metadata.find({}, projection={'_id': False})\ +# .sort('id', pymongo.ASCENDING) +# +# assert cursor.collection.count_documents({}) == 3 +# assert list(cursor) == metadata +# +#@pytest.mark.skip +#def test_get_metadata(): +# from planetmint.backend import connect, query +# conn = connect() +# +# metadata = [ +# {'id': 1, 'metadata': None}, +# {'id': 2, 'metadata': {'key': 'value'}}, +# {'id': 3, 'metadata': '3'}, +# ] +# +# conn.db.metadata.insert_many(deepcopy(metadata), ordered=False) +# +# for meta in metadata: +# assert query.get_metadata(conn, [meta['id']]) +# +#@pytest.mark.skip +#def test_get_owned_ids(signed_create_tx, user_pk): +# from planetmint.backend import connect, query +# conn = connect() +# +# # insert a transaction +# conn.db.transactions.insert_one(deepcopy(signed_create_tx.to_dict())) +# +# txns = list(query.get_owned_ids(conn, user_pk)) +# +# assert txns[0] == signed_create_tx.to_dict() +# +#@pytest.mark.skip +#def test_get_spending_transactions(user_pk, user_sk): +# from planetmint.backend import connect, query +# conn = connect() +# +# out = [([user_pk], 1)] +# tx1 = Create.generate([user_pk], out * 3) +# tx1.sign([user_sk]) +# inputs = tx1.to_inputs() +# tx2 = Transfer.generate([inputs[0]], out, tx1.id).sign([user_sk]) +# tx3 = Transfer.generate([inputs[1]], out, tx1.id).sign([user_sk]) +# tx4 = Transfer.generate([inputs[2]], out, tx1.id).sign([user_sk]) +# txns = [deepcopy(tx.to_dict()) for tx in [tx1, tx2, tx3, tx4]] +# conn.db.transactions.insert_many(txns) +# +# links = [inputs[0].fulfills.to_dict(), inputs[2].fulfills.to_dict()] +# txns = list(query.get_spending_transactions(conn, links)) +# +# # tx3 not a member because input 1 not asked for +# assert txns == [tx2.to_dict(), tx4.to_dict()] +# +#@pytest.mark.skip +#def test_get_spending_transactions_multiple_inputs(): +# from planetmint.backend import connect, query +# from planetmint.transactions.common.crypto import generate_key_pair +# conn = connect() +# (alice_sk, alice_pk) = generate_key_pair() +# (bob_sk, bob_pk) = generate_key_pair() +# (carol_sk, carol_pk) = generate_key_pair() +# +# out = [([alice_pk], 9)] +# tx1 = Create.generate([alice_pk], out).sign([alice_sk]) +# +# inputs1 = tx1.to_inputs() +# tx2 = Transfer.generate([inputs1[0]], +# [([alice_pk], 6), ([bob_pk], 3)], +# tx1.id).sign([alice_sk]) +# +# inputs2 = tx2.to_inputs() +# tx3 = Transfer.generate([inputs2[0]], +# [([bob_pk], 3), ([carol_pk], 3)], +# tx1.id).sign([alice_sk]) +# +# inputs3 = tx3.to_inputs() +# tx4 = Transfer.generate([inputs2[1], inputs3[0]], +# [([carol_pk], 6)], +# tx1.id).sign([bob_sk]) +# +# txns = [deepcopy(tx.to_dict()) for tx in [tx1, tx2, tx3, tx4]] +# conn.db.transactions.insert_many(txns) +# +# links = [ +# ({'transaction_id': tx2.id, 'output_index': 0}, 1, [tx3.id]), +# ({'transaction_id': tx2.id, 'output_index': 1}, 1, [tx4.id]), +# ({'transaction_id': tx3.id, 'output_index': 0}, 1, [tx4.id]), +# ({'transaction_id': tx3.id, 'output_index': 1}, 0, None), +# ] +# for li, num, match in links: +# txns = list(query.get_spending_transactions(conn, [li])) +# assert len(txns) == num +# if len(txns): +# assert [tx['id'] for tx in txns] == match +# +#@pytest.mark.skip +#def test_store_block(): +# from planetmint.backend import connect, query +# from planetmint.lib import Block +# conn = connect() +# +# block = Block(app_hash='random_utxo', +# height=3, +# transactions=[]) +# query.store_block(conn, block._asdict()) +# cursor = conn.db.blocks.find({}, projection={'_id': False}) +# assert cursor.collection.count_documents({}) == 1 +# +#@pytest.mark.skip +#def test_get_block(): +# from planetmint.backend import connect, query +# from planetmint.lib import Block +# conn = connect() +# +# block = Block(app_hash='random_utxo', +# height=3, +# transactions=[]) +# +# conn.db.blocks.insert_one(block._asdict()) +# +# block = dict(query.get_block(conn, 3)) +# assert block['height'] == 3 +# +#@pytest.mark.skip +#def test_delete_zero_unspent_outputs(db_context, utxoset): +# from planetmint.backend import query +# unspent_outputs, utxo_collection = utxoset +# delete_res = query.delete_unspent_outputs(db_context.conn) +# assert delete_res is None +# assert utxo_collection.count_documents({}) == 3 +# assert utxo_collection.count_documents( +# {'$or': [ +# {'transaction_id': 'a', 'output_index': 0}, +# {'transaction_id': 'b', 'output_index': 0}, +# {'transaction_id': 'a', 'output_index': 1}, +# ]} +# ) == 3 +# +#@pytest.mark.skip +#def test_delete_one_unspent_outputs(db_context, utxoset): +# from planetmint.backend import query +# unspent_outputs, utxo_collection = utxoset +# delete_res = query.delete_unspent_outputs(db_context.conn, +# unspent_outputs[0]) +# assert delete_res.raw_result['n'] == 1 +# assert utxo_collection.count_documents( +# {'$or': [ +# {'transaction_id': 'a', 'output_index': 1}, +# {'transaction_id': 'b', 'output_index': 0}, +# ]} +# ) == 2 +# assert utxo_collection.count_documents( +# {'transaction_id': 'a', 'output_index': 0}) == 0 +# +#@pytest.mark.skip +#def test_delete_many_unspent_outputs(db_context, utxoset): +# from planetmint.backend import query +# unspent_outputs, utxo_collection = utxoset +# delete_res = query.delete_unspent_outputs(db_context.conn, +# *unspent_outputs[::2]) +# assert delete_res.raw_result['n'] == 2 +# assert utxo_collection.count_documents( +# {'$or': [ +# {'transaction_id': 'a', 'output_index': 0}, +# {'transaction_id': 'b', 'output_index': 0}, +# ]} +# ) == 0 +# assert utxo_collection.count_documents( +# {'transaction_id': 'a', 'output_index': 1}) == 1 +# +#@pytest.mark.skip +#def test_store_zero_unspent_output(db_context, utxo_collection): +# from planetmint.backend import query +# res = query.store_unspent_outputs(db_context.conn) +# assert res is None +# assert utxo_collection.count_documents({}) == 0 +# +#@pytest.mark.skip +#def test_store_one_unspent_output(db_context, +# unspent_output_1, utxo_collection): +# from planetmint.backend import query +# res = query.store_unspent_outputs(db_context.conn, unspent_output_1) +# assert res.acknowledged +# assert len(res.inserted_ids) == 1 +# assert utxo_collection.count_documents( +# {'transaction_id': unspent_output_1['transaction_id'], +# 'output_index': unspent_output_1['output_index']} +# ) == 1 +# +#@pytest.mark.skip +#def test_store_many_unspent_outputs(db_context, +# unspent_outputs, utxo_collection): +# from planetmint.backend import query +# res = query.store_unspent_outputs(db_context.conn, *unspent_outputs) +# assert res.acknowledged +# assert len(res.inserted_ids) == 3 +# assert utxo_collection.count_documents( +# {'transaction_id': unspent_outputs[0]['transaction_id']} +# ) == 3 +# +#@pytest.mark.skip +#def test_get_unspent_outputs(db_context, utxoset): +# from planetmint.backend import query +# cursor = query.get_unspent_outputs(db_context.conn) +# assert cursor.collection.count_documents({}) == 3 +# retrieved_utxoset = list(cursor) +# unspent_outputs, utxo_collection = utxoset +# assert retrieved_utxoset == list( +# utxo_collection.find(projection={'_id': False})) +# assert retrieved_utxoset == unspent_outputs +# +#@pytest.mark.skip +#def test_store_pre_commit_state(db_context): +# from planetmint.backend import query +# +# state = dict(height=3, transactions=[]) +# +# query.store_pre_commit_state(db_context.conn, state) +# cursor = db_context.conn.db.pre_commit.find({'commit_id': 'test'}, +# projection={'_id': False}) +# assert cursor.collection.count_documents({}) == 1 +# +#@pytest.mark.skip +#def test_get_pre_commit_state(db_context): +# from planetmint.backend import query +# +# state = dict(height=3, transactions=[]) +# db_context.conn.db.pre_commit.insert_one(state) +# resp = query.get_pre_commit_state(db_context.conn) +# assert resp == state +# +#@pytest.mark.skip +#def test_validator_update(): +# from planetmint.backend import connect, query +# +# conn = connect() +# +# def gen_validator_update(height): +# return {'data': 'somedata', 'height': height, 'election_id': f'election_id_at_height_{height}'} +# +# for i in range(1, 100, 10): +# value = gen_validator_update(i) +# query.store_validator_set(conn, value) +# +# v1 = query.get_validator_set(conn, 8) +# assert v1['height'] == 1 +# +# v41 = query.get_validator_set(conn, 50) +# assert v41['height'] == 41 +# +# v91 = query.get_validator_set(conn) +# assert v91['height'] == 91 +# +#@pytest.mark.skip +#@pytest.mark.parametrize('description,stores,expected', [ +# ( +# 'Query empty database.', +# [], +# None, +# ), +# ( +# 'Store one chain with the default value for `is_synced`.', +# [ +# {'height': 0, 'chain_id': 'some-id'}, +# ], +# {'height': 0, 'chain_id': 'some-id', 'is_synced': True}, +# ), +# ( +# 'Store one chain with a custom value for `is_synced`.', +# [ +# {'height': 0, 'chain_id': 'some-id', 'is_synced': False}, +# ], +# {'height': 0, 'chain_id': 'some-id', 'is_synced': False}, +# ), +# ( +# 'Store one chain, then update it.', +# [ +# {'height': 0, 'chain_id': 'some-id', 'is_synced': True}, +# {'height': 0, 'chain_id': 'new-id', 'is_synced': False}, +# ], +# {'height': 0, 'chain_id': 'new-id', 'is_synced': False}, +# ), +# ( +# 'Store a chain, update it, store another chain.', +# [ +# {'height': 0, 'chain_id': 'some-id', 'is_synced': True}, +# {'height': 0, 'chain_id': 'some-id', 'is_synced': False}, +# {'height': 10, 'chain_id': 'another-id', 'is_synced': True}, +# ], +# {'height': 10, 'chain_id': 'another-id', 'is_synced': True}, +# ), +#]) +#def test_store_abci_chain(description, stores, expected): +# conn = connect() +# +# for store in stores: +# query.store_abci_chain(conn, **store) +# +# actual = query.get_latest_abci_chain(conn) +# assert expected == actual, description diff --git a/tests/backend/tarantool/test_queries.py b/tests/backend/tarantool/test_queries.py index a71e26a..7435df6 100644 --- a/tests/backend/tarantool/test_queries.py +++ b/tests/backend/tarantool/test_queries.py @@ -7,6 +7,9 @@ from copy import deepcopy import pytest +from planetmint.transactions.types.assets.create import Create +from planetmint.transactions.types.assets.transfer import Transfer + # import pymongo # # from planetmint.backend.connection import Connection, query @@ -232,12 +235,12 @@ def test_get_spending_transactions(user_pk, user_sk, db_conn): conn = db_conn.get_connection() out = [([user_pk], 1)] - tx1 = Transaction.create([user_pk], out * 3) + tx1 = Create.generate([user_pk], out * 3) tx1.sign([user_sk]) inputs = tx1.to_inputs() - tx2 = Transaction.transfer([inputs[0]], out, tx1.id).sign([user_sk]) - tx3 = Transaction.transfer([inputs[1]], out, tx1.id).sign([user_sk]) - tx4 = Transaction.transfer([inputs[2]], out, tx1.id).sign([user_sk]) + tx2 = Transfer.generate([inputs[0]], out, tx1.id).sign([user_sk]) + tx3 = Transfer.generate([inputs[1]], out, tx1.id).sign([user_sk]) + tx4 = Transfer.generate([inputs[2]], out, tx1.id).sign([user_sk]) txns = [deepcopy(tx.to_dict()) for tx in [tx1, tx2, tx3, tx4]] query.store_transactions(signed_transactions=txns, connection=conn) @@ -250,7 +253,7 @@ def test_get_spending_transactions(user_pk, user_sk, db_conn): def test_get_spending_transactions_multiple_inputs(db_conn): from planetmint.models import Transaction - from planetmint.common.crypto import generate_key_pair + from planetmint.transactions.common.crypto import generate_key_pair from planetmint.backend.tarantool import query conn = db_conn.get_connection() @@ -259,20 +262,20 @@ def test_get_spending_transactions_multiple_inputs(db_conn): (carol_sk, carol_pk) = generate_key_pair() out = [([alice_pk], 9)] - tx1 = Transaction.create([alice_pk], out).sign([alice_sk]) + tx1 = Create.generate([alice_pk], out).sign([alice_sk]) inputs1 = tx1.to_inputs() - tx2 = Transaction.transfer([inputs1[0]], + tx2 = Transfer.generate([inputs1[0]], [([alice_pk], 6), ([bob_pk], 3)], tx1.id).sign([alice_sk]) inputs2 = tx2.to_inputs() - tx3 = Transaction.transfer([inputs2[0]], + tx3 = Transfer.generate([inputs2[0]], [([bob_pk], 3), ([carol_pk], 3)], tx1.id).sign([alice_sk]) inputs3 = tx3.to_inputs() - tx4 = Transaction.transfer([inputs2[1], inputs3[0]], + tx4 = Transfer.generate([inputs2[1], inputs3[0]], [([carol_pk], 6)], tx1.id).sign([bob_sk]) diff --git a/tests/backend/test_connection.py b/tests/backend/test_connection.py index bcf0c1d..47abeeb 100644 --- a/tests/backend/test_connection.py +++ b/tests/backend/test_connection.py @@ -7,9 +7,8 @@ import pytest def test_get_connection_raises_a_configuration_error(monkeypatch): - from planetmint.common.exceptions import ConfigurationError + from planetmint.transactions.common.exceptions import ConfigurationError from planetmint.backend.connection import Connection - with pytest.raises(ConfigurationError): Connection('msaccess', 'localhost', '1337', 'mydb') diff --git a/tests/commands/conftest.py b/tests/commands/conftest.py index 72b81cd..521912f 100644 --- a/tests/commands/conftest.py +++ b/tests/commands/conftest.py @@ -34,7 +34,7 @@ def mock_processes_start(monkeypatch): @pytest.fixture def mock_generate_key_pair(monkeypatch): - monkeypatch.setattr('planetmint.common.crypto.generate_key_pair', lambda: ('privkey', 'pubkey')) + monkeypatch.setattr('planetmint.transactions.common.crypto.generate_key_pair', lambda: ('privkey', 'pubkey')) @pytest.fixture diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index b1e73ad..73c7dd3 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -14,7 +14,7 @@ import pytest from planetmint.config import Config from planetmint import ValidatorElection from planetmint.commands.planetmint import run_election_show -from planetmint.elections.election import Election +from planetmint.transactions.types.elections.election import Election from planetmint.lib import Block from planetmint.migrations.chain_migration_election import ChainMigrationElection @@ -124,9 +124,9 @@ def test_drop_db_when_interactive_yes(mock_db_drop, monkeypatch): @patch('planetmint.backend.schema.drop_database') def test_drop_db_when_db_does_not_exist(mock_db_drop, capsys): - from planetmint.config import Config - from planetmint.common.exceptions import DatabaseDoesNotExist + from planetmint.transactions.common.exceptions import DatabaseDoesNotExist from planetmint.commands.planetmint import run_drop + args = Namespace(config=None, yes=True) mock_db_drop.side_effect = DatabaseDoesNotExist @@ -263,16 +263,16 @@ def test_recover_db_on_start(mock_run_recover, @pytest.mark.bdb def test_run_recover(b, alice, bob): from planetmint.commands.planetmint import run_recover - from planetmint.models import Transaction + from planetmint.transactions.types.assets.create import Create from planetmint.lib import Block from planetmint.backend import query - tx1 = Transaction.create([alice.public_key], + tx1 = Create.generate([alice.public_key], [([alice.public_key], 1)], asset={'cycle': 'hero'}, metadata={'name': 'hohenheim'}) \ - .sign([alice.private_key]) - tx2 = Transaction.create([bob.public_key], + .sign([alice.private_key]) + tx2 = Create.generate([bob.public_key], [([bob.public_key], 1)], asset={'cycle': 'hero'}, metadata={'name': 'hohenheim'}) \ @@ -409,7 +409,7 @@ def test_election_new_upsert_validator_invalid_election(caplog, b, priv_validato @pytest.mark.bdb def test_election_new_upsert_validator_invalid_power(caplog, b, priv_validator_path, user_sk): from planetmint.commands.planetmint import run_election_new_upsert_validator - from planetmint.common.exceptions import InvalidPowerChange + from planetmint.transactions.common.exceptions import InvalidPowerChange def mock_write(tx, mode): b.store_bulk_transactions([tx]) diff --git a/tests/common/conftest.py b/tests/common/conftest.py index 1f8d029..eea23ee 100644 --- a/tests/common/conftest.py +++ b/tests/common/conftest.py @@ -100,31 +100,31 @@ def user2_Ed25519(user2_pub): @pytest.fixture def user_input(user_Ed25519, user_pub): - from planetmint.common.transaction import Input + from planetmint.transactions.common.transaction import Input return Input(user_Ed25519, [user_pub]) @pytest.fixture def user_user2_threshold_output(user_user2_threshold, user_pub, user2_pub): - from planetmint.common.transaction import Output + from planetmint.transactions.common.transaction import Output return Output(user_user2_threshold, [user_pub, user2_pub]) @pytest.fixture def user_user2_threshold_input(user_user2_threshold, user_pub, user2_pub): - from planetmint.common.transaction import Input + from planetmint.transactions.common.transaction import Input return Input(user_user2_threshold, [user_pub, user2_pub]) @pytest.fixture def user_output(user_Ed25519, user_pub): - from planetmint.common.transaction import Output + from planetmint.transactions.common.transaction import Output return Output(user_Ed25519, [user_pub]) @pytest.fixture def user2_output(user2_Ed25519, user2_pub): - from planetmint.common.transaction import Output + from planetmint.transactions.common.transaction import Output return Output(user2_Ed25519, [user2_pub]) @@ -140,7 +140,7 @@ def data(): @pytest.fixture def utx(user_input, user_output): - from planetmint.common.transaction import Transaction + from planetmint.transactions.common.transaction import Transaction return Transaction(Transaction.CREATE, {'data': None}, [user_input], [user_output]) @@ -152,8 +152,8 @@ def tx(utx, user_priv): @pytest.fixture def transfer_utx(user_output, user2_output, utx): - from planetmint.common.transaction import (Input, TransactionLink, - Transaction) + from planetmint.transactions.common.transaction import ( + Input, TransactionLink, Transaction) user_output = user_output.to_dict() input = Input(utx.outputs[0].fulfillment, user_output['public_keys'], diff --git a/tests/common/test_memoize.py b/tests/common/test_memoize.py index f8a35c5..dd047cb 100644 --- a/tests/common/test_memoize.py +++ b/tests/common/test_memoize.py @@ -7,8 +7,9 @@ import pytest from copy import deepcopy from planetmint.models import Transaction -from planetmint.common.crypto import generate_key_pair -from planetmint.common.memoize import to_dict, from_dict +from planetmint.transactions.types.assets.create import Create +from planetmint.transactions.common.crypto import generate_key_pair +from planetmint.transactions.common.memoize import to_dict, from_dict pytestmark = pytest.mark.bdb @@ -23,7 +24,7 @@ def test_memoize_to_dict(b): assert to_dict.cache_info().hits == 0 assert to_dict.cache_info().misses == 0 - tx = Transaction.create([alice.public_key], + tx = Create.generate([alice.public_key], [([alice.public_key], 1)], asset=asset,)\ .sign([alice.private_key]) @@ -49,7 +50,7 @@ def test_memoize_from_dict(b): assert from_dict.cache_info().hits == 0 assert from_dict.cache_info().misses == 0 - tx = Transaction.create([alice.public_key], + tx = Create.generate([alice.public_key], [([alice.public_key], 1)], asset=asset,)\ .sign([alice.private_key]) @@ -76,7 +77,7 @@ def test_memoize_input_valid(b): assert Transaction._input_valid.cache_info().hits == 0 assert Transaction._input_valid.cache_info().misses == 0 - tx = Transaction.create([alice.public_key], + tx = Create.generate([alice.public_key], [([alice.public_key], 1)], asset=asset,)\ .sign([alice.private_key]) diff --git a/tests/common/test_schema.py b/tests/common/test_schema.py index 62269b4..07cda88 100644 --- a/tests/common/test_schema.py +++ b/tests/common/test_schema.py @@ -13,8 +13,8 @@ from hypothesis import given from hypothesis.strategies import from_regex as regex from pytest import raises -from planetmint.common.exceptions import SchemaValidationError -from planetmint.common.schema import ( +from planetmint.transactions.common.exceptions import SchemaValidationError +from planetmint.transactions.common.schema import ( TX_SCHEMA_COMMON, validate_transaction_schema, ) diff --git a/tests/common/test_transaction.py b/tests/common/test_transaction.py index 268b714..691e475 100644 --- a/tests/common/test_transaction.py +++ b/tests/common/test_transaction.py @@ -11,6 +11,8 @@ from copy import deepcopy from base58 import b58encode, b58decode from cryptoconditions import Ed25519Sha256 +from planetmint.transactions.types.assets.create import Create +from planetmint.transactions.types.assets.transfer import Transfer from pytest import mark, raises try: from hashlib import sha3_256 @@ -21,7 +23,7 @@ pytestmark = mark.bdb def test_input_serialization(ffill_uri, user_pub): - from planetmint.common.transaction import Input + from planetmint.transactions.common.transaction import Input from cryptoconditions import Fulfillment expected = { @@ -34,7 +36,7 @@ def test_input_serialization(ffill_uri, user_pub): def test_input_deserialization_with_uri(ffill_uri, user_pub): - from planetmint.common.transaction import Input + from planetmint.transactions.common.transaction import Input from cryptoconditions import Fulfillment expected = Input(Fulfillment.from_uri(ffill_uri), [user_pub]) @@ -50,7 +52,7 @@ def test_input_deserialization_with_uri(ffill_uri, user_pub): @mark.skip(reason='None is tolerated because it is None before fulfilling.') def test_input_deserialization_with_invalid_input(user_pub): - from planetmint.common.transaction import Input + from planetmint.transactions.common.transaction import Input ffill = { 'owners_before': [user_pub], @@ -62,8 +64,8 @@ def test_input_deserialization_with_invalid_input(user_pub): def test_input_deserialization_with_invalid_fulfillment_uri(user_pub): - from planetmint.common.exceptions import InvalidSignature - from planetmint.common.transaction import Input + from planetmint.transactions.common.exceptions import InvalidSignature + from planetmint.transactions.common.transaction import Input ffill = { 'owners_before': [user_pub], @@ -75,7 +77,7 @@ def test_input_deserialization_with_invalid_fulfillment_uri(user_pub): def test_input_deserialization_with_unsigned_fulfillment(ffill_uri, user_pub): - from planetmint.common.transaction import Input + from planetmint.transactions.common.transaction import Input from cryptoconditions import Fulfillment expected = Input(Fulfillment.from_uri(ffill_uri), [user_pub]) @@ -90,7 +92,7 @@ def test_input_deserialization_with_unsigned_fulfillment(ffill_uri, user_pub): def test_output_serialization(user_Ed25519, user_pub): - from planetmint.common.transaction import Output + from planetmint.transactions.common.transaction import Output expected = { 'condition': { @@ -110,7 +112,7 @@ def test_output_serialization(user_Ed25519, user_pub): def test_output_deserialization(user_Ed25519, user_pub): - from planetmint.common.transaction import Output + from planetmint.transactions.common.transaction import Output expected = Output(user_Ed25519, [user_pub], 1) cond = { @@ -130,7 +132,7 @@ def test_output_deserialization(user_Ed25519, user_pub): def test_output_hashlock_serialization(): - from planetmint.common.transaction import Output + from planetmint.transactions.common.transaction import Output from cryptoconditions import PreimageSha256 secret = b'wow much secret' @@ -149,7 +151,7 @@ def test_output_hashlock_serialization(): def test_output_hashlock_deserialization(): - from planetmint.common.transaction import Output + from planetmint.transactions.common.transaction import Output from cryptoconditions import PreimageSha256 secret = b'wow much secret' @@ -169,8 +171,8 @@ def test_output_hashlock_deserialization(): def test_invalid_output_initialization(cond_uri, user_pub): - from planetmint.common.transaction import Output - from planetmint.common.exceptions import AmountError + from planetmint.transactions.common.transaction import Output + from planetmint.transactions.common.exceptions import AmountError with raises(TypeError): Output(cond_uri, user_pub) @@ -181,7 +183,7 @@ def test_invalid_output_initialization(cond_uri, user_pub): def test_generate_output_split_half_recursive(user_pub, user2_pub, user3_pub): - from planetmint.common.transaction import Output + from planetmint.transactions.common.transaction import Output from cryptoconditions import Ed25519Sha256, ThresholdSha256 expected_simple1 = Ed25519Sha256(public_key=b58decode(user_pub)) @@ -201,7 +203,7 @@ def test_generate_output_split_half_recursive(user_pub, user2_pub, user3_pub): def test_generate_outputs_split_half_single_owner(user_pub, user2_pub, user3_pub): - from planetmint.common.transaction import Output + from planetmint.transactions.common.transaction import Output from cryptoconditions import Ed25519Sha256, ThresholdSha256 expected_simple1 = Ed25519Sha256(public_key=b58decode(user_pub)) @@ -220,7 +222,7 @@ def test_generate_outputs_split_half_single_owner(user_pub, def test_generate_outputs_flat_ownage(user_pub, user2_pub, user3_pub): - from planetmint.common.transaction import Output + from planetmint.transactions.common.transaction import Output from cryptoconditions import Ed25519Sha256, ThresholdSha256 expected_simple1 = Ed25519Sha256(public_key=b58decode(user_pub)) @@ -237,7 +239,7 @@ def test_generate_outputs_flat_ownage(user_pub, user2_pub, user3_pub): def test_generate_output_single_owner(user_pub): - from planetmint.common.transaction import Output + from planetmint.transactions.common.transaction import Output from cryptoconditions import Ed25519Sha256 expected = Ed25519Sha256(public_key=b58decode(user_pub)) @@ -247,7 +249,7 @@ def test_generate_output_single_owner(user_pub): def test_generate_output_single_owner_with_output(user_pub): - from planetmint.common.transaction import Output + from planetmint.transactions.common.transaction import Output from cryptoconditions import Ed25519Sha256 expected = Ed25519Sha256(public_key=b58decode(user_pub)) @@ -257,8 +259,8 @@ def test_generate_output_single_owner_with_output(user_pub): def test_generate_output_invalid_parameters(user_pub, user2_pub, user3_pub): - from planetmint.common.transaction import Output - from planetmint.common.exceptions import AmountError + from planetmint.transactions.common.transaction import Output + from planetmint.transactions.common.exceptions import AmountError with raises(ValueError): Output.generate([], 1) @@ -273,7 +275,7 @@ def test_generate_output_invalid_parameters(user_pub, user2_pub, user3_pub): def test_invalid_transaction_initialization(asset_definition): - from planetmint.common.transaction import Transaction + from planetmint.transactions.common.transaction import Transaction with raises(ValueError): Transaction(operation='invalid operation', asset=asset_definition) @@ -305,7 +307,7 @@ def test_invalid_transaction_initialization(asset_definition): def test_create_default_asset_on_tx_initialization(asset_definition): - from planetmint.common.transaction import Transaction + from planetmint.transactions.common.transaction import Transaction expected = {'data': None} tx = Transaction(Transaction.CREATE, asset=expected) @@ -315,7 +317,7 @@ def test_create_default_asset_on_tx_initialization(asset_definition): def test_transaction_serialization(user_input, user_output, data): - from planetmint.common.transaction import Transaction + from planetmint.transactions.common.transaction import Transaction expected = { 'id': None, @@ -339,14 +341,14 @@ def test_transaction_serialization(user_input, user_output, data): def test_transaction_deserialization(tri_state_transaction): - from planetmint.common.transaction import Transaction + from planetmint.transactions.common.transaction import Transaction from .utils import validate_transaction_model tx = Transaction.from_dict(tri_state_transaction) validate_transaction_model(tx) def test_invalid_input_initialization(user_input, user_pub): - from planetmint.common.transaction import Input + from planetmint.transactions.common.transaction import Input with raises(TypeError): Input(user_input, user_pub) @@ -355,7 +357,7 @@ def test_invalid_input_initialization(user_input, user_pub): def test_transaction_link_serialization(): - from planetmint.common.transaction import TransactionLink + from planetmint.transactions.common.transaction import TransactionLink tx_id = 'a transaction id' expected = { @@ -368,7 +370,7 @@ def test_transaction_link_serialization(): def test_transaction_link_serialization_with_empty_payload(): - from planetmint.common.transaction import TransactionLink + from planetmint.transactions.common.transaction import TransactionLink expected = None tx_link = TransactionLink() @@ -377,7 +379,7 @@ def test_transaction_link_serialization_with_empty_payload(): def test_transaction_link_deserialization(): - from planetmint.common.transaction import TransactionLink + from planetmint.transactions.common.transaction import TransactionLink tx_id = 'a transaction id' expected = TransactionLink(tx_id, 0) @@ -391,7 +393,7 @@ def test_transaction_link_deserialization(): def test_transaction_link_deserialization_with_empty_payload(): - from planetmint.common.transaction import TransactionLink + from planetmint.transactions.common.transaction import TransactionLink expected = TransactionLink() tx_link = TransactionLink.from_dict(None) @@ -400,7 +402,7 @@ def test_transaction_link_deserialization_with_empty_payload(): def test_transaction_link_empty_to_uri(): - from planetmint.common.transaction import TransactionLink + from planetmint.transactions.common.transaction import TransactionLink expected = None tx_link = TransactionLink().to_uri() @@ -409,7 +411,7 @@ def test_transaction_link_empty_to_uri(): def test_transaction_link_to_uri(): - from planetmint.common.transaction import TransactionLink + from planetmint.transactions.common.transaction import TransactionLink expected = 'path/transactions/abc/outputs/0' tx_link = TransactionLink('abc', 0).to_uri('path') @@ -418,7 +420,7 @@ def test_transaction_link_to_uri(): def test_cast_transaction_link_to_boolean(): - from planetmint.common.transaction import TransactionLink + from planetmint.transactions.common.transaction import TransactionLink assert bool(TransactionLink()) is False assert bool(TransactionLink('a', None)) is False @@ -428,7 +430,7 @@ def test_cast_transaction_link_to_boolean(): def test_transaction_link_eq(): - from planetmint.common.transaction import TransactionLink + from planetmint.transactions.common.transaction import TransactionLink assert TransactionLink(1, 2) == TransactionLink(1, 2) assert TransactionLink(2, 2) != TransactionLink(1, 2) @@ -437,7 +439,7 @@ def test_transaction_link_eq(): def test_add_input_to_tx(user_input, asset_definition): - from planetmint.common.transaction import Transaction + from planetmint.transactions.common.transaction import Transaction from .utils import validate_transaction_model tx = Transaction(Transaction.CREATE, asset_definition, [], []) @@ -449,7 +451,7 @@ def test_add_input_to_tx(user_input, asset_definition): def test_add_input_to_tx_with_invalid_parameters(asset_definition): - from planetmint.common.transaction import Transaction + from planetmint.transactions.common.transaction import Transaction tx = Transaction(Transaction.CREATE, asset_definition) with raises(TypeError): @@ -457,7 +459,7 @@ def test_add_input_to_tx_with_invalid_parameters(asset_definition): def test_add_output_to_tx(user_output, user_input, asset_definition): - from planetmint.common.transaction import Transaction + from planetmint.transactions.common.transaction import Transaction from .utils import validate_transaction_model tx = Transaction(Transaction.CREATE, asset_definition, [user_input]) @@ -469,7 +471,7 @@ def test_add_output_to_tx(user_output, user_input, asset_definition): def test_add_output_to_tx_with_invalid_parameters(asset_definition): - from planetmint.common.transaction import Transaction + from planetmint.transactions.common.transaction import Transaction tx = Transaction(Transaction.CREATE, asset_definition, [], []) with raises(TypeError): @@ -485,7 +487,7 @@ def test_sign_with_invalid_parameters(utx, user_priv): def test_validate_tx_simple_create_signature(user_input, user_output, user_priv, asset_definition): - from planetmint.common.transaction import Transaction + from planetmint.transactions.common.transaction import Transaction from .utils import validate_transaction_model tx = Transaction(Transaction.CREATE, asset_definition, [user_input], [user_output]) @@ -507,7 +509,7 @@ def test_validate_tx_simple_create_signature(user_input, user_output, user_priv, def test_invoke_simple_signature_fulfillment_with_invalid_params(utx, user_input): - from planetmint.common.exceptions import KeypairMismatchException + from planetmint.transactions.common.exceptions import KeypairMismatchException with raises(KeypairMismatchException): invalid_key_pair = {'wrong_pub_key': 'wrong_priv_key'} @@ -518,7 +520,7 @@ def test_invoke_simple_signature_fulfillment_with_invalid_params(utx, def test_sign_threshold_with_invalid_params(utx, user_user2_threshold_input, user3_pub, user3_priv): - from planetmint.common.exceptions import KeypairMismatchException + from planetmint.transactions.common.exceptions import KeypairMismatchException with raises(KeypairMismatchException): utx._sign_threshold_signature_fulfillment(user_user2_threshold_input, @@ -532,7 +534,7 @@ def test_sign_threshold_with_invalid_params(utx, user_user2_threshold_input, def test_validate_input_with_invalid_parameters(utx): - from planetmint.common.transaction import Transaction + from planetmint.transactions.common.transaction import Transaction input_conditions = [out.fulfillment.condition_uri for out in utx.outputs] tx_dict = utx.to_dict() @@ -548,7 +550,7 @@ def test_validate_tx_threshold_create_signature(user_user2_threshold_input, user_priv, user2_priv, asset_definition): - from planetmint.common.transaction import Transaction + from planetmint.transactions.common.transaction import Transaction from .utils import validate_transaction_model tx = Transaction(Transaction.CREATE, asset_definition, @@ -576,7 +578,7 @@ def test_validate_tx_threshold_create_signature(user_user2_threshold_input, def test_validate_tx_threshold_duplicated_pk(user_pub, user_priv, asset_definition): from cryptoconditions import Ed25519Sha256, ThresholdSha256 - from planetmint.common.transaction import Input, Output, Transaction + from planetmint.transactions.common.transaction import Input, Output, Transaction threshold = ThresholdSha256(threshold=2) threshold.add_subfulfillment( @@ -621,8 +623,8 @@ def test_multiple_input_validation_of_transfer_tx(user_input, user_output, user2_priv, user3_pub, user3_priv, asset_definition): - from planetmint.common.transaction import (Transaction, TransactionLink, - Input, Output) + from planetmint.transactions.common.transaction import ( + Transaction, TransactionLink, Input, Output) from cryptoconditions import Ed25519Sha256 from .utils import validate_transaction_model @@ -647,7 +649,7 @@ def test_multiple_input_validation_of_transfer_tx(user_input, user_output, def test_validate_inputs_of_transfer_tx_with_invalid_params( transfer_tx, cond_uri, utx, user2_pub, user_priv, ffill_uri): - from planetmint.common.transaction import Output + from planetmint.transactions.common.transaction import Output from cryptoconditions import Ed25519Sha256 invalid_out = Output(Ed25519Sha256.from_uri(ffill_uri), ['invalid']) @@ -668,7 +670,7 @@ def test_validate_inputs_of_transfer_tx_with_invalid_params( def test_create_create_transaction_single_io(user_output, user_pub, data): - from planetmint.common.transaction import Transaction + from planetmint.transactions.common.transaction import Transaction from .utils import validate_transaction_model expected = { @@ -690,7 +692,7 @@ def test_create_create_transaction_single_io(user_output, user_pub, data): 'version': Transaction.VERSION, } - tx = Transaction.create([user_pub], [([user_pub], 1)], metadata=data, + tx = Create.generate([user_pub], [([user_pub], 1)], metadata=data, asset=data) tx_dict = tx.to_dict() tx_dict['inputs'][0]['fulfillment'] = None @@ -703,16 +705,15 @@ def test_create_create_transaction_single_io(user_output, user_pub, data): def test_validate_single_io_create_transaction(user_pub, user_priv, data, asset_definition): - from planetmint.common.transaction import Transaction - tx = Transaction.create([user_pub], [([user_pub], 1)], metadata=data) + tx = Create.generate([user_pub], [([user_pub], 1)], metadata=data) tx = tx.sign([user_priv]) assert tx.inputs_valid() is True def test_create_create_transaction_multiple_io(user_output, user2_output, user_pub, user2_pub, asset_definition): - from planetmint.common.transaction import Transaction, Input + from planetmint.transactions.common.transaction import Transaction, Input # a fulfillment for a create transaction with multiple `owners_before` # is a fulfillment for an implicit threshold condition with @@ -727,7 +728,7 @@ def test_create_create_transaction_multiple_io(user_output, user2_output, user_p 'operation': 'CREATE', 'version': Transaction.VERSION } - tx = Transaction.create([user_pub, user2_pub], + tx = Create.generate([user_pub, user2_pub], [([user_pub], 1), ([user2_pub], 1)], metadata={'message': 'hello'}).to_dict() tx.pop('id') @@ -739,10 +740,9 @@ def test_create_create_transaction_multiple_io(user_output, user2_output, user_p def test_validate_multiple_io_create_transaction(user_pub, user_priv, user2_pub, user2_priv, asset_definition): - from planetmint.common.transaction import Transaction from .utils import validate_transaction_model - tx = Transaction.create([user_pub, user2_pub], + tx = Create.generate([user_pub, user2_pub], [([user_pub], 1), ([user2_pub], 1)], metadata={'message': 'hello'}) tx = tx.sign([user_priv, user2_priv]) @@ -754,7 +754,7 @@ def test_validate_multiple_io_create_transaction(user_pub, user_priv, def test_create_create_transaction_threshold(user_pub, user2_pub, user3_pub, user_user2_threshold_output, user_user2_threshold_input, data): - from planetmint.common.transaction import Transaction + from planetmint.transactions.common.transaction import Transaction expected = { 'outputs': [user_user2_threshold_output.to_dict()], @@ -774,7 +774,7 @@ def test_create_create_transaction_threshold(user_pub, user2_pub, user3_pub, 'operation': 'CREATE', 'version': Transaction.VERSION } - tx = Transaction.create([user_pub], [([user_pub, user2_pub], 1)], + tx = Create.generate([user_pub], [([user_pub, user2_pub], 1)], metadata=data, asset=data) tx_dict = tx.to_dict() tx_dict.pop('id') @@ -785,10 +785,9 @@ def test_create_create_transaction_threshold(user_pub, user2_pub, user3_pub, def test_validate_threshold_create_transaction(user_pub, user_priv, user2_pub, data, asset_definition): - from planetmint.common.transaction import Transaction from .utils import validate_transaction_model - tx = Transaction.create([user_pub], [([user_pub, user2_pub], 1)], + tx = Create.generate([user_pub], [([user_pub, user2_pub], 1)], metadata=data) tx = tx.sign([user_priv]) assert tx.inputs_valid() is True @@ -797,25 +796,23 @@ def test_validate_threshold_create_transaction(user_pub, user_priv, user2_pub, def test_create_create_transaction_with_invalid_parameters(user_pub): - from planetmint.common.transaction import Transaction - with raises(TypeError): - Transaction.create('not a list') + Create.generate('not a list') with raises(TypeError): - Transaction.create([], 'not a list') + Create.generate([], 'not a list') with raises(ValueError): - Transaction.create([], [user_pub]) + Create.generate([], [user_pub]) with raises(ValueError): - Transaction.create([user_pub], []) + Create.generate([user_pub], []) with raises(ValueError): - Transaction.create([user_pub], [user_pub]) + Create.generate([user_pub], [user_pub]) with raises(ValueError): - Transaction.create([user_pub], [([user_pub],)]) + Create.generate([user_pub], [([user_pub],)]) with raises(TypeError): - Transaction.create([user_pub], [([user_pub], 1)], + Create.generate([user_pub], [([user_pub], 1)], metadata='not a dict or none') with raises(TypeError): - Transaction.create([user_pub], + Create.generate([user_pub], [([user_pub], 1)], asset='not a dict or none') @@ -832,7 +829,7 @@ def test_outputs_to_inputs(tx): def test_create_transfer_transaction_single_io(tx, user_pub, user2_pub, user2_output, user_priv): - from planetmint.common.transaction import Transaction + from planetmint.transactions.common.transaction import Transaction from .utils import validate_transaction_model expected = { @@ -858,7 +855,7 @@ def test_create_transfer_transaction_single_io(tx, user_pub, user2_pub, 'version': Transaction.VERSION } inputs = tx.to_inputs([0]) - transfer_tx = Transaction.transfer(inputs, [([user2_pub], 1)], + transfer_tx = Transfer.generate(inputs, [([user2_pub], 1)], asset_id=tx.id) transfer_tx = transfer_tx.sign([user_priv]) transfer_tx = transfer_tx.to_dict() @@ -887,9 +884,9 @@ def test_create_transfer_transaction_multiple_io(user_pub, user_priv, user2_pub, user2_priv, user3_pub, user2_output, asset_definition): - from planetmint.common.transaction import Transaction + from planetmint.transactions.common.transaction import Transaction - tx = Transaction.create([user_pub], [([user_pub], 1), ([user2_pub], 1)], + tx = Create.generate([user_pub], [([user_pub], 1), ([user2_pub], 1)], metadata={'message': 'hello'}) tx = tx.sign([user_priv]) @@ -921,7 +918,7 @@ def test_create_transfer_transaction_multiple_io(user_pub, user_priv, 'version': Transaction.VERSION } - transfer_tx = Transaction.transfer(tx.to_inputs(), + transfer_tx = Transfer.generate(tx.to_inputs(), [([user2_pub], 1), ([user2_pub], 1)], asset_id=tx.id) transfer_tx = transfer_tx.sign([user_priv, user2_priv]) @@ -941,30 +938,28 @@ def test_create_transfer_transaction_multiple_io(user_pub, user_priv, def test_create_transfer_with_invalid_parameters(tx, user_pub): - from planetmint.common.transaction import Transaction - with raises(TypeError): - Transaction.transfer({}, [], tx.id) + Transfer.generate({}, [], tx.id) with raises(ValueError): - Transaction.transfer([], [], tx.id) + Transfer.generate([], [], tx.id) with raises(TypeError): - Transaction.transfer(['fulfillment'], {}, tx.id) + Transfer.generate(['fulfillment'], {}, tx.id) with raises(ValueError): - Transaction.transfer(['fulfillment'], [], tx.id) + Transfer.generate(['fulfillment'], [], tx.id) with raises(ValueError): - Transaction.transfer(['fulfillment'], [user_pub], tx.id) + Transfer.generate(['fulfillment'], [user_pub], tx.id) with raises(ValueError): - Transaction.transfer(['fulfillment'], [([user_pub],)], tx.id) + Transfer.generate(['fulfillment'], [([user_pub],)], tx.id) with raises(TypeError): - Transaction.transfer(['fulfillment'], [([user_pub], 1)], + Transfer.generate(['fulfillment'], [([user_pub], 1)], tx.id, metadata='not a dict or none') with raises(TypeError): - Transaction.transfer(['fulfillment'], [([user_pub], 1)], + Transfer.generate(['fulfillment'], [([user_pub], 1)], ['not a string']) def test_cant_add_empty_output(): - from planetmint.common.transaction import Transaction + from planetmint.transactions.common.transaction import Transaction tx = Transaction(Transaction.CREATE, None) with raises(TypeError): @@ -972,7 +967,7 @@ def test_cant_add_empty_output(): def test_cant_add_empty_input(): - from planetmint.common.transaction import Transaction + from planetmint.transactions.common.transaction import Transaction tx = Transaction(Transaction.CREATE, None) with raises(TypeError): @@ -980,7 +975,7 @@ def test_cant_add_empty_input(): def test_unfulfilled_transaction_serialized(unfulfilled_transaction): - from planetmint.common.transaction import Transaction + from planetmint.transactions.common.transaction import Transaction tx_obj = Transaction.from_dict(unfulfilled_transaction) expected = json.dumps(unfulfilled_transaction, sort_keys=True, separators=(',', ':'), ensure_ascii=True) @@ -988,7 +983,7 @@ def test_unfulfilled_transaction_serialized(unfulfilled_transaction): def test_fulfilled_transaction_serialized(fulfilled_transaction): - from planetmint.common.transaction import Transaction + from planetmint.transactions.common.transaction import Transaction tx_obj = Transaction.from_dict(fulfilled_transaction) expected = json.dumps(fulfilled_transaction, sort_keys=True, separators=(',', ':'), ensure_ascii=True) @@ -996,7 +991,7 @@ def test_fulfilled_transaction_serialized(fulfilled_transaction): def test_transaction_hash(fulfilled_transaction): - from planetmint.common.transaction import Transaction + from planetmint.transactions.common.transaction import Transaction tx_obj = Transaction.from_dict(fulfilled_transaction) assert tx_obj._id is None assert tx_obj.id is None @@ -1009,8 +1004,8 @@ def test_transaction_hash(fulfilled_transaction): def test_output_from_dict_invalid_amount(user_output): - from planetmint.common.transaction import Output - from planetmint.common.exceptions import AmountError + from planetmint.transactions.common.transaction import Output + from planetmint.transactions.common.exceptions import AmountError out = user_output.to_dict() out['amount'] = 'a' @@ -1019,8 +1014,7 @@ def test_output_from_dict_invalid_amount(user_output): def test_unspent_outputs_property(merlin, alice, bob, carol): - from planetmint.common.transaction import Transaction - tx = Transaction.create( + tx = Create.generate( [merlin.public_key], [([alice.public_key], 1), ([bob.public_key], 2), diff --git a/tests/common/utils.py b/tests/common/utils.py index d95575e..bd10303 100644 --- a/tests/common/utils.py +++ b/tests/common/utils.py @@ -5,8 +5,8 @@ def validate_transaction_model(tx): - from planetmint.common.transaction import Transaction - from planetmint.common.schema import validate_transaction_schema + from planetmint.transactions.common.transaction import Transaction + from planetmint.transactions.common.schema import validate_transaction_schema tx_dict = tx.to_dict() # Check that a transaction is valid by re-serializing it diff --git a/tests/conftest.py b/tests/conftest.py index d416f77..e321344 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -14,6 +14,7 @@ import os import copy import random import tempfile +import codecs from collections import namedtuple from logging import getLogger from logging.config import dictConfig @@ -23,19 +24,22 @@ from planetmint.backend.tarantool.connection import TarantoolDB import pytest # from pymongo import MongoClient -#from planetmint.upsert_validator import ValidatorElection -from planetmint.common import crypto -from planetmint.common.transaction_mode_types import BROADCAST_TX_COMMIT +from planetmint import ValidatorElection +from planetmint.transactions.common import crypto +from planetmint.transactions.common.transaction_mode_types import BROADCAST_TX_COMMIT from planetmint.tendermint_utils import key_from_base64 from planetmint.backend import schema, query -from planetmint.common.crypto import (key_pair_from_ed25519_key, - public_key_from_ed25519_key) -from planetmint.common.exceptions import DatabaseDoesNotExist +from planetmint.transactions.common.crypto import ( + key_pair_from_ed25519_key, public_key_from_ed25519_key) +from planetmint.transactions.common.exceptions import DatabaseDoesNotExist from planetmint.lib import Block from tests.utils import gen_vote from planetmint.config import Config from planetmint.upsert_validator import ValidatorElection +from tendermint.abci import types_pb2 as types +from tendermint.crypto import keys_pb2 + TEST_DB_NAME = 'planetmint_test' USER2_SK, USER2_PK = crypto.generate_key_pair() @@ -45,6 +49,15 @@ USER_PRIVATE_KEY = '8eJ8q9ZQpReWyQT5aFCiwtZ5wDZC4eDnCen88p3tQ6ie' USER_PUBLIC_KEY = 'JEAkEJqLbbgDRAtMm8YAjGp759Aq2qTn9eaEHUj2XePE' +@pytest.fixture +def init_chain_request(): + pk = codecs.decode(b'VAgFZtYw8bNR5TMZHFOBDWk9cAmEu3/c6JgRBmddbbI=', + 'base64') + val_a = types.ValidatorUpdate(power=10, + pub_key=keys_pb2.PublicKey(ed25519=pk)) + return types.RequestInitChain(validators=[val_a]) + + def pytest_addoption(parser): from planetmint.backend.connection import BACKENDS @@ -132,9 +145,10 @@ def _setup_database(_configure_planetmint): # TODO Here is located setup databa @pytest.fixture + def _bdb(_setup_database ): from planetmint.backend import Connection - from planetmint.common.memoize import to_dict, from_dict + from planetmint.transactions.common.memoize import to_dict, from_dict from planetmint.models import Transaction conn = Connection() yield @@ -188,13 +202,13 @@ def user2_pk(): @pytest.fixture def alice(): - from planetmint.common.crypto import generate_key_pair + from planetmint.transactions.common.crypto import generate_key_pair return generate_key_pair() @pytest.fixture def bob(): - from planetmint.common.crypto import generate_key_pair + from planetmint.transactions.common.crypto import generate_key_pair return generate_key_pair() @@ -210,7 +224,7 @@ def bob_pubkey(carol): @pytest.fixture def carol(): - from planetmint.common.crypto import generate_key_pair + from planetmint.transactions.common.crypto import generate_key_pair return generate_key_pair() @@ -226,14 +240,15 @@ def carol_pubkey(carol): @pytest.fixture def merlin(): - from planetmint.common.crypto import generate_key_pair + from planetmint.transactions.common.crypto import generate_key_pair return generate_key_pair() @pytest.fixture -def a(): - from abci import types_v0_31_5 - return types_v0_31_5 +# def a(): +def abci_fixture(): + from tendermint.abci import types_pb2 + return types_pb2 @pytest.fixture @@ -241,11 +256,14 @@ def b(): from planetmint import Planetmint return Planetmint() +@pytest.fixture +def eventqueue_fixture(): + from multiprocessing import Queue + return Queue() @pytest.fixture def b_mock(b, network_validators): b.get_validators = mock_get_validators(network_validators) - return b @@ -264,9 +282,9 @@ def mock_get_validators(network_validators): @pytest.fixture def create_tx(alice, user_pk): - from planetmint.models import Transaction + from planetmint.transactions.types.assets.create import Create name = f'I am created by the create_tx fixture. My random identifier is {random.random()}.' - return Transaction.create([alice.public_key], [([user_pk], 1)], asset={'name': name}) + return Create.generate([alice.public_key], [([user_pk], 1)], asset={'name': name}) @pytest.fixture @@ -283,17 +301,17 @@ def posted_create_tx(b, signed_create_tx): @pytest.fixture def signed_transfer_tx(signed_create_tx, user_pk, user_sk): - from planetmint.models import Transaction + from planetmint.transactions.types.assets.transfer import Transfer inputs = signed_create_tx.to_inputs() - tx = Transaction.transfer(inputs, [([user_pk], 1)], asset_id=signed_create_tx.id) + tx = Transfer.generate(inputs, [([user_pk], 1)], asset_id=signed_create_tx.id) return tx.sign([user_sk]) @pytest.fixture def double_spend_tx(signed_create_tx, carol_pubkey, user_sk): - from planetmint.models import Transaction + from planetmint.transactions.types.assets.transfer import Transfer inputs = signed_create_tx.to_inputs() - tx = Transaction.transfer( + tx = Transfer.generate( inputs, [([carol_pubkey], 1)], asset_id=signed_create_tx.id) return tx.sign([user_sk]) @@ -305,11 +323,11 @@ def _get_height(b): @pytest.fixture def inputs(user_pk, b, alice): - from planetmint.models import Transaction + from planetmint.transactions.types.assets.create import Create # create blocks with transactions for `USER` to spend for height in range(1, 4): transactions = [ - Transaction.create( + Create.generate( [alice.public_key], [([user_pk], 1)], metadata={'msg': random.random()}, @@ -439,11 +457,11 @@ def event_loop(): @pytest.fixture(scope='session') def abci_server(): from abci.server import ABCIServer - from abci import types_v0_31_5 + # from tendermint.abci import types_pb2 as types_v0_34_11 from planetmint.core import App from planetmint.utils import Process - app = ABCIServer(app=App(types_v0_31_5)) + app = ABCIServer(app=App()) abci_proxy = Process(name='ABCI', target=app.run) yield abci_proxy.start() abci_proxy.terminate() diff --git a/tests/db/test_bigchain_api.py b/tests/db/test_planetmint_api.py similarity index 79% rename from tests/db/test_bigchain_api.py rename to tests/db/test_planetmint_api.py index 0ac7686..3ce7077 100644 --- a/tests/db/test_bigchain_api.py +++ b/tests/db/test_planetmint_api.py @@ -4,6 +4,8 @@ # Code is Apache-2.0 and docs are CC-BY-4.0 import warnings from unittest.mock import patch +from planetmint.transactions.types.assets.create import Create +from planetmint.transactions.types.assets.transfer import Transfer import pytest from base58 import b58decode @@ -14,19 +16,18 @@ pytestmark = pytest.mark.bdb class TestBigchainApi(object): def test_get_spent_with_double_spend_detected(self, b, alice): - from planetmint.models import Transaction - from planetmint.common.exceptions import DoubleSpend + from planetmint.transactions.common.exceptions import DoubleSpend from planetmint.exceptions import CriticalDoubleSpend - tx = Transaction.create([alice.public_key], [([alice.public_key], 1)]) + tx = Create.generate([alice.public_key], [([alice.public_key], 1)]) tx = tx.sign([alice.private_key]) b.store_bulk_transactions([tx]) - transfer_tx = Transaction.transfer(tx.to_inputs(), [([alice.public_key], 1)], + transfer_tx = Transfer.generate(tx.to_inputs(), [([alice.public_key], 1)], asset_id=tx.id) transfer_tx = transfer_tx.sign([alice.private_key]) - transfer_tx2 = Transaction.transfer(tx.to_inputs(), [([alice.public_key], 2)], + transfer_tx2 = Transfer.generate(tx.to_inputs(), [([alice.public_key], 2)], asset_id=tx.id) transfer_tx2 = transfer_tx2.sign([alice.private_key]) @@ -44,12 +45,11 @@ class TestBigchainApi(object): b.get_spent(tx.id, 0) def test_double_inclusion(self, b, alice): - from planetmint.models import Transaction from planetmint.backend.exceptions import OperationError from tarantool.error import DatabaseError from planetmint.backend.tarantool.connection import TarantoolDB - tx = Transaction.create([alice.public_key], [([alice.public_key], 1)]) + tx = Create.generate([alice.public_key], [([alice.public_key], 1)]) tx = tx.sign([alice.private_key]) b.store_bulk_transactions([tx]) @@ -61,7 +61,6 @@ class TestBigchainApi(object): b.store_bulk_transactions([tx]) def test_text_search(self, b, alice): - from planetmint.models import Transaction from planetmint.backend.tarantool.connection import TarantoolDB if isinstance(b.connection, TarantoolDB): @@ -74,11 +73,11 @@ class TestBigchainApi(object): asset3 = {'msg': 'Planetmint 3'} # create the transactions - tx1 = Transaction.create([alice.public_key], [([alice.public_key], 1)], + tx1 = Create.generate([alice.public_key], [([alice.public_key], 1)], asset=asset1).sign([alice.private_key]) - tx2 = Transaction.create([alice.public_key], [([alice.public_key], 1)], + tx2 = Create.generate([alice.public_key], [([alice.public_key], 1)], asset=asset2).sign([alice.private_key]) - tx3 = Transaction.create([alice.public_key], [([alice.public_key], 1)], + tx3 = Create.generate([alice.public_key], [([alice.public_key], 1)], asset=asset3).sign([alice.private_key]) # write the transactions to the DB @@ -91,25 +90,23 @@ class TestBigchainApi(object): @pytest.mark.usefixtures('inputs') def test_non_create_input_not_found(self, b, user_pk): from cryptoconditions import Ed25519Sha256 - from planetmint.common.exceptions import InputDoesNotExist - from planetmint.common.transaction import Input, TransactionLink - from planetmint.models import Transaction + from planetmint.transactions.common.exceptions import InputDoesNotExist + from planetmint.transactions.common.transaction import Input, TransactionLink # Create an input for a non existing transaction input = Input(Ed25519Sha256(public_key=b58decode(user_pk)), [user_pk], TransactionLink('somethingsomething', 0)) - tx = Transaction.transfer([input], [([user_pk], 1)], + tx = Transfer.generate([input], [([user_pk], 1)], asset_id='mock_asset_link') with pytest.raises(InputDoesNotExist): tx.validate(b) def test_write_transaction(self, b, user_sk, user_pk, alice, create_tx): - from planetmint.models import Transaction asset1 = {'msg': 'Planetmint 1'} - tx = Transaction.create([alice.public_key], [([alice.public_key], 1)], + tx = Create.generate([alice.public_key], [([alice.public_key], 1)], asset=asset1).sign([alice.private_key]) b.store_bulk_transactions([tx]) @@ -127,8 +124,8 @@ class TestBigchainApi(object): class TestTransactionValidation(object): def test_non_create_input_not_found(self, b, signed_transfer_tx): - from planetmint.common.exceptions import InputDoesNotExist - from planetmint.common.transaction import TransactionLink + from planetmint.transactions.common.exceptions import InputDoesNotExist + from planetmint.transactions.common.transaction import TransactionLink signed_transfer_tx.inputs[0].fulfills = TransactionLink('c', 0) with pytest.raises(InputDoesNotExist): @@ -136,14 +133,13 @@ class TestTransactionValidation(object): @pytest.mark.usefixtures('inputs') def test_non_create_valid_input_wrong_owner(self, b, user_pk): - from planetmint.common.crypto import generate_key_pair - from planetmint.common.exceptions import InvalidSignature - from planetmint.models import Transaction + from planetmint.transactions.common.crypto import generate_key_pair + from planetmint.transactions.common.exceptions import InvalidSignature input_tx = b.fastquery.get_outputs_by_public_key(user_pk).pop() input_transaction = b.get_transaction(input_tx.txid) sk, pk = generate_key_pair() - tx = Transaction.create([pk], [([user_pk], 1)]) + tx = Create.generate([pk], [([user_pk], 1)]) tx.operation = 'TRANSFER' tx.asset = {'id': input_transaction.id} tx.inputs[0].fulfills = input_tx @@ -154,7 +150,7 @@ class TestTransactionValidation(object): @pytest.mark.usefixtures('inputs') def test_non_create_double_spend(self, b, signed_create_tx, signed_transfer_tx, double_spend_tx): - from planetmint.common.exceptions import DoubleSpend + from planetmint.transactions.common.exceptions import DoubleSpend b.store_bulk_transactions([signed_create_tx, signed_transfer_tx]) @@ -166,15 +162,14 @@ class TestMultipleInputs(object): def test_transfer_single_owner_single_input(self, b, inputs, user_pk, user_sk): - from planetmint.common import crypto - from planetmint.models import Transaction + from planetmint.transactions.common import crypto user2_sk, user2_pk = crypto.generate_key_pair() tx_link = b.fastquery.get_outputs_by_public_key(user_pk).pop() input_tx = b.get_transaction(tx_link.txid) inputs = input_tx.to_inputs() - tx = Transaction.transfer(inputs, [([user2_pk], 1)], + tx = Transfer.generate(inputs, [([user2_pk], 1)], asset_id=input_tx.id) tx = tx.sign([user_sk]) @@ -187,15 +182,14 @@ class TestMultipleInputs(object): user_sk, user_pk, inputs): - from planetmint.common import crypto - from planetmint.models import Transaction + from planetmint.transactions.common import crypto user2_sk, user2_pk = crypto.generate_key_pair() user3_sk, user3_pk = crypto.generate_key_pair() tx_link = b.fastquery.get_outputs_by_public_key(user_pk).pop() input_tx = b.get_transaction(tx_link.txid) - tx = Transaction.transfer(input_tx.to_inputs(), + tx = Transfer.generate(input_tx.to_inputs(), [([user2_pk, user3_pk], 1)], asset_id=input_tx.id) tx = tx.sign([user_sk]) @@ -209,13 +203,12 @@ class TestMultipleInputs(object): user_sk, user_pk, alice): - from planetmint.common import crypto - from planetmint.models import Transaction + from planetmint.transactions.common import crypto user2_sk, user2_pk = crypto.generate_key_pair() user3_sk, user3_pk = crypto.generate_key_pair() - tx = Transaction.create([alice.public_key], [([user_pk, user2_pk], 1)]) + tx = Create.generate([alice.public_key], [([user_pk, user2_pk], 1)]) tx = tx.sign([alice.private_key]) b.store_bulk_transactions([tx]) @@ -223,7 +216,7 @@ class TestMultipleInputs(object): input_tx = b.get_transaction(owned_input.txid) inputs = input_tx.to_inputs() - transfer_tx = Transaction.transfer(inputs, [([user3_pk], 1)], + transfer_tx = Transfer.generate(inputs, [([user3_pk], 1)], asset_id=input_tx.id) transfer_tx = transfer_tx.sign([user_sk, user2_sk]) @@ -237,14 +230,13 @@ class TestMultipleInputs(object): user_sk, user_pk, alice): - from planetmint.common import crypto - from planetmint.models import Transaction + from planetmint.transactions.common import crypto user2_sk, user2_pk = crypto.generate_key_pair() user3_sk, user3_pk = crypto.generate_key_pair() user4_sk, user4_pk = crypto.generate_key_pair() - tx = Transaction.create([alice.public_key], [([user_pk, user2_pk], 1)]) + tx = Create.generate([alice.public_key], [([user_pk, user2_pk], 1)]) tx = tx.sign([alice.private_key]) b.store_bulk_transactions([tx]) @@ -252,7 +244,7 @@ class TestMultipleInputs(object): tx_link = b.fastquery.get_outputs_by_public_key(user_pk).pop() tx_input = b.get_transaction(tx_link.txid) - tx = Transaction.transfer(tx_input.to_inputs(), + tx = Transfer.generate(tx_input.to_inputs(), [([user3_pk, user4_pk], 1)], asset_id=tx_input.id) tx = tx.sign([user_sk, user2_sk]) @@ -262,13 +254,12 @@ class TestMultipleInputs(object): assert len(tx.outputs) == 1 def test_get_owned_ids_single_tx_single_output(self, b, user_sk, user_pk, alice): - from planetmint.common import crypto - from planetmint.common.transaction import TransactionLink - from planetmint.models import Transaction + from planetmint.transactions.common import crypto + from planetmint.transactions.common.transaction import TransactionLink user2_sk, user2_pk = crypto.generate_key_pair() - tx = Transaction.create([alice.public_key], [([user_pk], 1)]) + tx = Create.generate([alice.public_key], [([user_pk], 1)]) tx = tx.sign([alice.private_key]) b.store_bulk_transactions([tx]) @@ -277,7 +268,7 @@ class TestMultipleInputs(object): assert owned_inputs_user1 == [TransactionLink(tx.id, 0)] assert owned_inputs_user2 == [] - tx_transfer = Transaction.transfer(tx.to_inputs(), [([user2_pk], 1)], + tx_transfer = Transfer.generate(tx.to_inputs(), [([user2_pk], 1)], asset_id=tx.id) tx_transfer = tx_transfer.sign([user_sk]) b.store_bulk_transactions([tx_transfer]) @@ -290,14 +281,13 @@ class TestMultipleInputs(object): def test_get_owned_ids_single_tx_multiple_outputs(self, b, user_sk, user_pk, alice): - from planetmint.common import crypto - from planetmint.common.transaction import TransactionLink - from planetmint.models import Transaction + from planetmint.transactions.common import crypto + from planetmint.transactions.common.transaction import TransactionLink user2_sk, user2_pk = crypto.generate_key_pair() # create divisible asset - tx_create = Transaction.create([alice.public_key], [([user_pk], 1), ([user_pk], 1)]) + tx_create = Create.generate([alice.public_key], [([user_pk], 1), ([user_pk], 1)]) tx_create_signed = tx_create.sign([alice.private_key]) b.store_bulk_transactions([tx_create_signed]) @@ -311,7 +301,7 @@ class TestMultipleInputs(object): assert owned_inputs_user2 == [] # transfer divisible asset divided in two outputs - tx_transfer = Transaction.transfer(tx_create.to_inputs(), + tx_transfer = Transfer.generate(tx_create.to_inputs(), [([user2_pk], 1), ([user2_pk], 1)], asset_id=tx_create.id) tx_transfer_signed = tx_transfer.sign([user_sk]) @@ -324,14 +314,13 @@ class TestMultipleInputs(object): TransactionLink(tx_transfer.id, 1)] def test_get_owned_ids_multiple_owners(self, b, user_sk, user_pk, alice): - from planetmint.common import crypto - from planetmint.common.transaction import TransactionLink - from planetmint.models import Transaction + from planetmint.transactions.common import crypto + from planetmint.transactions.common.transaction import TransactionLink user2_sk, user2_pk = crypto.generate_key_pair() user3_sk, user3_pk = crypto.generate_key_pair() - tx = Transaction.create([alice.public_key], [([user_pk, user2_pk], 1)]) + tx = Create.generate([alice.public_key], [([user_pk, user2_pk], 1)]) tx = tx.sign([alice.private_key]) b.store_bulk_transactions([tx]) @@ -343,7 +332,7 @@ class TestMultipleInputs(object): assert owned_inputs_user1 == owned_inputs_user2 assert owned_inputs_user1 == expected_owned_inputs_user1 - tx = Transaction.transfer(tx.to_inputs(), [([user3_pk], 1)], + tx = Transfer.generate(tx.to_inputs(), [([user3_pk], 1)], asset_id=tx.id) tx = tx.sign([user_sk, user2_sk]) b.store_bulk_transactions([tx]) @@ -356,12 +345,11 @@ class TestMultipleInputs(object): assert not spent_user1 def test_get_spent_single_tx_single_output(self, b, user_sk, user_pk, alice): - from planetmint.common import crypto - from planetmint.models import Transaction + from planetmint.transactions.common import crypto user2_sk, user2_pk = crypto.generate_key_pair() - tx = Transaction.create([alice.public_key], [([user_pk], 1)]) + tx = Create.generate([alice.public_key], [([user_pk], 1)]) tx = tx.sign([alice.private_key]) b.store_bulk_transactions([tx]) @@ -373,7 +361,7 @@ class TestMultipleInputs(object): assert spent_inputs_user1 is None # create a transaction and send it - tx = Transaction.transfer(tx.to_inputs(), [([user2_pk], 1)], + tx = Transfer.generate(tx.to_inputs(), [([user2_pk], 1)], asset_id=tx.id) tx = tx.sign([user_sk]) b.store_bulk_transactions([tx]) @@ -382,14 +370,13 @@ class TestMultipleInputs(object): assert spent_inputs_user1 == tx def test_get_spent_single_tx_multiple_outputs(self, b, user_sk, user_pk, alice): - from planetmint.common import crypto - from planetmint.models import Transaction + from planetmint.transactions.common import crypto # create a new users user2_sk, user2_pk = crypto.generate_key_pair() # create a divisible asset with 3 outputs - tx_create = Transaction.create([alice.public_key], + tx_create = Create.generate([alice.public_key], [([user_pk], 1), ([user_pk], 1), ([user_pk], 1)]) @@ -403,7 +390,7 @@ class TestMultipleInputs(object): assert b.get_spent(input_tx.txid, input_tx.output) is None # transfer the first 2 inputs - tx_transfer = Transaction.transfer(tx_create.to_inputs()[:2], + tx_transfer = Transfer.generate(tx_create.to_inputs()[:2], [([user2_pk], 1), ([user2_pk], 1)], asset_id=tx_create.id) tx_transfer_signed = tx_transfer.sign([user_sk]) @@ -419,8 +406,7 @@ class TestMultipleInputs(object): assert b.get_spent(tx_create.to_inputs()[2].fulfills.txid, 2) is None def test_get_spent_multiple_owners(self, b, user_sk, user_pk, alice): - from planetmint.common import crypto - from planetmint.models import Transaction + from planetmint.transactions.common import crypto user2_sk, user2_pk = crypto.generate_key_pair() user3_sk, user3_pk = crypto.generate_key_pair() @@ -428,7 +414,7 @@ class TestMultipleInputs(object): transactions = [] for i in range(3): payload = {'somedata': i} - tx = Transaction.create([alice.public_key], [([user_pk, user2_pk], 1)], + tx = Create.generate([alice.public_key], [([user_pk, user2_pk], 1)], payload) tx = tx.sign([alice.private_key]) transactions.append(tx) @@ -441,7 +427,7 @@ class TestMultipleInputs(object): assert b.get_spent(input_tx.txid, input_tx.output) is None # create a transaction - tx = Transaction.transfer(transactions[0].to_inputs(), + tx = Transfer.generate(transactions[0].to_inputs(), [([user3_pk], 1)], asset_id=transactions[0].id) tx = tx.sign([user_sk, user2_sk]) @@ -455,7 +441,7 @@ class TestMultipleInputs(object): def test_get_outputs_filtered_only_unspent(): - from planetmint.common.transaction import TransactionLink + from planetmint.transactions.common.transaction import TransactionLink from planetmint.lib import Planetmint go = 'planetmint.fastquery.FastQuery.get_outputs_by_public_key' @@ -471,7 +457,7 @@ def test_get_outputs_filtered_only_unspent(): def test_get_outputs_filtered_only_spent(): - from planetmint.common.transaction import TransactionLink + from planetmint.transactions.common.transaction import TransactionLink from planetmint.lib import Planetmint go = 'planetmint.fastquery.FastQuery.get_outputs_by_public_key' with patch(go) as get_outputs: @@ -488,7 +474,7 @@ def test_get_outputs_filtered_only_spent(): @patch('planetmint.fastquery.FastQuery.filter_unspent_outputs') @patch('planetmint.fastquery.FastQuery.filter_spent_outputs') def test_get_outputs_filtered(filter_spent, filter_unspent): - from planetmint.common.transaction import TransactionLink + from planetmint.transactions.common.transaction import TransactionLink from planetmint.lib import Planetmint go = 'planetmint.fastquery.FastQuery.get_outputs_by_public_key' @@ -506,18 +492,17 @@ def test_cant_spend_same_input_twice_in_tx(b, alice): """Recreate duplicated fulfillments bug https://github.com/planetmint/planetmint/issues/1099 """ - from planetmint.models import Transaction - from planetmint.common.exceptions import DoubleSpend + from planetmint.transactions.common.exceptions import DoubleSpend # create a divisible asset - tx_create = Transaction.create([alice.public_key], [([alice.public_key], 100)]) + tx_create = Create.generate([alice.public_key], [([alice.public_key], 100)]) tx_create_signed = tx_create.sign([alice.private_key]) assert b.validate_transaction(tx_create_signed) == tx_create_signed b.store_bulk_transactions([tx_create_signed]) # Create a transfer transaction with duplicated fulfillments dup_inputs = tx_create.to_inputs() + tx_create.to_inputs() - tx_transfer = Transaction.transfer(dup_inputs, [([alice.public_key], 200)], + tx_transfer = Transfer.generate(dup_inputs, [([alice.public_key], 200)], asset_id=tx_create.id) tx_transfer_signed = tx_transfer.sign([alice.private_key]) with pytest.raises(DoubleSpend): @@ -526,14 +511,13 @@ def test_cant_spend_same_input_twice_in_tx(b, alice): def test_transaction_unicode(b, alice): import copy - from planetmint.common.utils import serialize - from planetmint.models import Transaction + from planetmint.transactions.common.utils import serialize # http://www.fileformat.info/info/unicode/char/1f37a/index.htm beer_python = {'beer': '\N{BEER MUG}'} beer_json = '{"beer":"\N{BEER MUG}"}' - tx = (Transaction.create([alice.public_key], [([alice.public_key], 100)], beer_python) + tx = (Create.generate([alice.public_key], [([alice.public_key], 100)], beer_python) ).sign([alice.private_key]) tx_1 = copy.deepcopy(tx) diff --git a/tests/elections/test_election.py b/tests/elections/test_election.py index 10c95a2..e58ec4f 100644 --- a/tests/elections/test_election.py +++ b/tests/elections/test_election.py @@ -3,11 +3,10 @@ import pytest from tests.utils import generate_election, generate_validators from planetmint.lib import Block -from planetmint.elections.election import Election +from planetmint.transactions.types.elections.election import Election from planetmint.migrations.chain_migration_election import ChainMigrationElection from planetmint.upsert_validator.validator_election import ValidatorElection - @pytest.mark.bdb def test_process_block_concludes_all_elections(b): validators = generate_validators([1] * 4) diff --git a/tests/tendermint/conftest.py b/tests/tendermint/conftest.py index aab19ec..e3f3ffd 100644 --- a/tests/tendermint/conftest.py +++ b/tests/tendermint/conftest.py @@ -6,8 +6,8 @@ import pytest import codecs -from abci import types_v0_31_5 as types - +from tendermint.abci import types_pb2 as types +from tendermint.crypto import keys_pb2 @pytest.fixture def validator_pub_key(): @@ -19,5 +19,5 @@ def init_chain_request(): pk = codecs.decode(b'VAgFZtYw8bNR5TMZHFOBDWk9cAmEu3/c6JgRBmddbbI=', 'base64') val_a = types.ValidatorUpdate(power=10, - pub_key=types.PubKey(type='ed25519', data=pk)) + pub_key=keys_pb2.PublicKey(ed25519=pk)) return types.RequestInitChain(validators=[val_a]) diff --git a/tests/tendermint/test_core.py b/tests/tendermint/test_core.py index dd77d9c..d50a38b 100644 --- a/tests/tendermint/test_core.py +++ b/tests/tendermint/test_core.py @@ -4,18 +4,21 @@ # Code is Apache-2.0 and docs are CC-BY-4.0 import json +from planetmint.transactions.types.assets.create import Create +from planetmint.transactions.types.assets.transfer import Transfer import pytest import random -from abci import types_v0_31_5 as types +from tendermint.abci import types_pb2 as types +from tendermint.crypto import keys_pb2 -from planetmint.core import App +from planetmint import App from planetmint.backend import query -from planetmint.common.crypto import generate_key_pair -from planetmint.core import (CodeTypeOk, +from planetmint.transactions.common.crypto import generate_key_pair +from planetmint.core import (OkCode, CodeTypeError, rollback) -from planetmint.elections.election import Election +from planetmint.transactions.types.elections.election import Election from planetmint.lib import Block from planetmint.migrations.chain_migration_election import ChainMigrationElection from planetmint.upsert_validator.validator_election import ValidatorElection @@ -40,7 +43,7 @@ def generate_address(): def generate_validator(): pk, _ = generate_key_pair() - pub_key = types.PubKey(type='ed25519', data=pk.encode()) + pub_key = keys_pb2.PublicKey(ed25519=pk.encode()) val = types.ValidatorUpdate(power=10, pub_key=pub_key) return val @@ -50,9 +53,9 @@ def generate_init_chain_request(chain_id, vals=None): return types.RequestInitChain(validators=vals, chain_id=chain_id) -def test_init_chain_successfully_registers_chain(a, b): +def test_init_chain_successfully_registers_chain(b): request = generate_init_chain_request('chain-XYZ') - res = App(a, b).init_chain(request) + res = App(b).init_chain(request) assert res == types.ResponseInitChain() chain = query.get_latest_abci_chain(b.connection) assert chain == {'height': 0, 'chain_id': 'chain-XYZ', 'is_synced': True} @@ -63,10 +66,10 @@ def test_init_chain_successfully_registers_chain(a, b): } -def test_init_chain_ignores_invalid_init_chain_requests(a, b): +def test_init_chain_ignores_invalid_init_chain_requests(b): validators = [generate_validator()] request = generate_init_chain_request('chain-XYZ', validators) - res = App(a, b).init_chain(request) + res = App(b).init_chain(request) assert res == types.ResponseInitChain() validator_set = query.get_validator_set(b.connection) @@ -80,7 +83,7 @@ def test_init_chain_ignores_invalid_init_chain_requests(a, b): ] for r in invalid_requests: with pytest.raises(SystemExit): - App(a, b).init_chain(r) + App(b).init_chain(r) # assert nothing changed - neither validator set, nor chain ID new_validator_set = query.get_validator_set(b.connection) assert new_validator_set == validator_set @@ -93,10 +96,10 @@ def test_init_chain_ignores_invalid_init_chain_requests(a, b): } -def test_init_chain_recognizes_new_chain_after_migration(a, b): +def test_init_chain_recognizes_new_chain_after_migration(b): validators = [generate_validator()] request = generate_init_chain_request('chain-XYZ', validators) - res = App(a, b).init_chain(request) + res = App(b).init_chain(request) assert res == types.ResponseInitChain() validator_set = query.get_validator_set(b.connection)['validators'] @@ -115,7 +118,7 @@ def test_init_chain_recognizes_new_chain_after_migration(a, b): ] for r in invalid_requests: with pytest.raises(SystemExit): - App(a, b).init_chain(r) + App(b).init_chain(r) assert query.get_latest_abci_chain(b.connection) == { 'chain_id': 'chain-XYZ-migrated-at-height-1', 'is_synced': False, @@ -128,7 +131,7 @@ def test_init_chain_recognizes_new_chain_after_migration(a, b): # completes the migration request = generate_init_chain_request('chain-XYZ-migrated-at-height-1', validators) - res = App(a, b).init_chain(request) + res = App(b).init_chain(request) assert res == types.ResponseInitChain() assert query.get_latest_abci_chain(b.connection) == { 'chain_id': 'chain-XYZ-migrated-at-height-1', @@ -149,7 +152,7 @@ def test_init_chain_recognizes_new_chain_after_migration(a, b): ] for r in invalid_requests: with pytest.raises(SystemExit): - App(a, b).init_chain(r) + App(b).init_chain(r) assert query.get_latest_abci_chain(b.connection) == { 'chain_id': 'chain-XYZ-migrated-at-height-1', 'is_synced': True, @@ -164,9 +167,9 @@ def test_init_chain_recognizes_new_chain_after_migration(a, b): } -def test_info(a, b): +def test_info(b): r = types.RequestInfo(version=__tm_supported_versions__[0]) - app = App(a, b) + app = App(b) res = app.info(r) assert res.last_block_height == 0 @@ -179,7 +182,7 @@ def test_info(a, b): # simulate a migration and assert the height is shifted b.store_abci_chain(2, 'chain-XYZ') - app = App(a, b) + app = App(b) b.store_block(Block(app_hash='2', height=2, transactions=[])._asdict()) res = app.info(r) assert res.last_block_height == 0 @@ -192,61 +195,58 @@ def test_info(a, b): # it's always the latest migration that is taken into account b.store_abci_chain(4, 'chain-XYZ-new') - app = App(a, b) + app = App(b) b.store_block(Block(app_hash='4', height=4, transactions=[])._asdict()) res = app.info(r) assert res.last_block_height == 0 assert res.last_block_app_hash == b'4' -def test_check_tx__signed_create_is_ok(a, b): +def test_check_tx__signed_create_is_ok(b): from planetmint import App - from planetmint.models import Transaction - from planetmint.common.crypto import generate_key_pair + from planetmint.transactions.common.crypto import generate_key_pair alice = generate_key_pair() bob = generate_key_pair() - tx = Transaction.create([alice.public_key], + tx = Create.generate([alice.public_key], [([bob.public_key], 1)])\ .sign([alice.private_key]) - app = App(a, b) + app = App(b) result = app.check_tx(encode_tx_to_bytes(tx)) - assert result.code == CodeTypeOk + assert result.code == OkCode -def test_check_tx__unsigned_create_is_error(a, b): +def test_check_tx__unsigned_create_is_error(b): from planetmint import App - from planetmint.models import Transaction - from planetmint.common.crypto import generate_key_pair + from planetmint.transactions.common.crypto import generate_key_pair alice = generate_key_pair() bob = generate_key_pair() - tx = Transaction.create([alice.public_key], + tx = Create.generate([alice.public_key], [([bob.public_key], 1)]) - app = App(a, b) + app = App(b) result = app.check_tx(encode_tx_to_bytes(tx)) assert result.code == CodeTypeError -def test_deliver_tx__valid_create_updates_db_and_emits_event(a, b, init_chain_request): +def test_deliver_tx__valid_create_updates_db_and_emits_event(b, init_chain_request): import multiprocessing as mp from planetmint import App - from planetmint.models import Transaction - from planetmint.common.crypto import generate_key_pair + from planetmint.transactions.common.crypto import generate_key_pair alice = generate_key_pair() bob = generate_key_pair() events = mp.Queue() - tx = Transaction.create([alice.public_key], + tx = Create.generate([alice.public_key], [([bob.public_key], 1)])\ .sign([alice.private_key]) - app = App(a, b, events) + app = App(b, events) app.init_chain(init_chain_request) @@ -254,7 +254,7 @@ def test_deliver_tx__valid_create_updates_db_and_emits_event(a, b, init_chain_re app.begin_block(begin_block) result = app.deliver_tx(encode_tx_to_bytes(tx)) - assert result.code == CodeTypeOk + assert result.code == OkCode app.end_block(types.RequestEndBlock(height=99)) app.commit() @@ -270,26 +270,25 @@ def test_deliver_tx__valid_create_updates_db_and_emits_event(a, b, init_chain_re # next(unspent_outputs) -def test_deliver_tx__double_spend_fails(a, b, init_chain_request): +def test_deliver_tx__double_spend_fails(b, init_chain_request): from planetmint import App - from planetmint.models import Transaction - from planetmint.common.crypto import generate_key_pair + from planetmint.transactions.common.crypto import generate_key_pair alice = generate_key_pair() bob = generate_key_pair() - tx = Transaction.create([alice.public_key], + tx = Create.generate([alice.public_key], [([bob.public_key], 1)])\ .sign([alice.private_key]) - app = App(a, b) + app = App(b) app.init_chain(init_chain_request) begin_block = types.RequestBeginBlock() app.begin_block(begin_block) result = app.deliver_tx(encode_tx_to_bytes(tx)) - assert result.code == CodeTypeOk + assert result.code == OkCode app.end_block(types.RequestEndBlock(height=99)) app.commit() @@ -298,12 +297,11 @@ def test_deliver_tx__double_spend_fails(a, b, init_chain_request): assert result.code == CodeTypeError -def test_deliver_transfer_tx__double_spend_fails(a, b, init_chain_request): +def test_deliver_transfer_tx__double_spend_fails(b, init_chain_request): from planetmint import App - from planetmint.models import Transaction - from planetmint.common.crypto import generate_key_pair + from planetmint.transactions.common.crypto import generate_key_pair - app = App(a, b) + app = App(b) app.init_chain(init_chain_request) begin_block = types.RequestBeginBlock() @@ -317,23 +315,23 @@ def test_deliver_transfer_tx__double_spend_fails(a, b, init_chain_request): 'msg': 'live long and prosper' } - tx = Transaction.create([alice.public_key], + tx = Create.generate([alice.public_key], [([alice.public_key], 1)], asset=asset)\ .sign([alice.private_key]) result = app.deliver_tx(encode_tx_to_bytes(tx)) - assert result.code == CodeTypeOk + assert result.code == OkCode - tx_transfer = Transaction.transfer(tx.to_inputs(), + tx_transfer = Transfer.generate(tx.to_inputs(), [([bob.public_key], 1)], asset_id=tx.id)\ .sign([alice.private_key]) result = app.deliver_tx(encode_tx_to_bytes(tx_transfer)) - assert result.code == CodeTypeOk + assert result.code == OkCode - double_spend = Transaction.transfer(tx.to_inputs(), + double_spend = Transfer.generate(tx.to_inputs(), [([carly.public_key], 1)], asset_id=tx.id)\ .sign([alice.private_key]) @@ -342,8 +340,8 @@ def test_deliver_transfer_tx__double_spend_fails(a, b, init_chain_request): assert result.code == CodeTypeError -def test_end_block_return_validator_updates(a, b, init_chain_request): - app = App(a, b) +def test_end_block_return_validator_updates(b, init_chain_request): + app = App(b) app.init_chain(init_chain_request) begin_block = types.RequestBeginBlock() @@ -374,20 +372,19 @@ def test_end_block_return_validator_updates(a, b, init_chain_request): resp = app.end_block(types.RequestEndBlock(height=2)) assert resp.validator_updates[0].power == new_validator['election']['power'] expected = bytes.fromhex(new_validator['election']['public_key']['value']) - assert expected == resp.validator_updates[0].pub_key.data + assert expected == resp.validator_updates[0].pub_key.ed25519 -def test_store_pre_commit_state_in_end_block(a, b, alice, init_chain_request): +def test_store_pre_commit_state_in_end_block(b, alice, init_chain_request): from planetmint import App from planetmint.backend import query - from planetmint.models import Transaction - tx = Transaction.create([alice.public_key], + tx = Create.generate([alice.public_key], [([alice.public_key], 1)], asset={'msg': 'live long and prosper'})\ .sign([alice.private_key]) - app = App(a, b) + app = App(b) app.init_chain(init_chain_request) begin_block = types.RequestBeginBlock() @@ -408,7 +405,7 @@ def test_store_pre_commit_state_in_end_block(a, b, alice, init_chain_request): # simulate a chain migration and assert the height is shifted b.store_abci_chain(100, 'new-chain') - app = App(a, b) + app = App(b) app.begin_block(begin_block) app.deliver_tx(encode_tx_to_bytes(tx)) app.end_block(types.RequestEndBlock(height=1)) @@ -501,43 +498,43 @@ def test_new_validator_set(b): assert updated_validator_set == updated_validators -def test_info_aborts_if_chain_is_not_synced(a, b): +def test_info_aborts_if_chain_is_not_synced(b): b.store_abci_chain(0, 'chain-XYZ', False) with pytest.raises(SystemExit): - App(a, b).info(types.RequestInfo()) + App(b).info(types.RequestInfo()) -def test_check_tx_aborts_if_chain_is_not_synced(a, b): +def test_check_tx_aborts_if_chain_is_not_synced(b): b.store_abci_chain(0, 'chain-XYZ', False) with pytest.raises(SystemExit): - App(a, b).check_tx('some bytes') + App(b).check_tx('some bytes') -def test_begin_aborts_if_chain_is_not_synced(a, b): +def test_begin_aborts_if_chain_is_not_synced(b): b.store_abci_chain(0, 'chain-XYZ', False) with pytest.raises(SystemExit): - App(a, b).info(types.RequestBeginBlock()) + App(b).info(types.RequestBeginBlock()) -def test_deliver_tx_aborts_if_chain_is_not_synced(a, b): +def test_deliver_tx_aborts_if_chain_is_not_synced(b): b.store_abci_chain(0, 'chain-XYZ', False) with pytest.raises(SystemExit): - App(a, b).deliver_tx('some bytes') + App(b).deliver_tx('some bytes') -def test_end_block_aborts_if_chain_is_not_synced(a, b): +def test_end_block_aborts_if_chain_is_not_synced(b): b.store_abci_chain(0, 'chain-XYZ', False) with pytest.raises(SystemExit): - App(a, b).info(types.RequestEndBlock()) + App(b).info(types.RequestEndBlock()) -def test_commit_aborts_if_chain_is_not_synced(a, b): +def test_commit_aborts_if_chain_is_not_synced(b): b.store_abci_chain(0, 'chain-XYZ', False) with pytest.raises(SystemExit): - App(a, b).commit() + App(b).commit() diff --git a/tests/tendermint/test_fastquery.py b/tests/tendermint/test_fastquery.py index 8f3b262..2b513d1 100644 --- a/tests/tendermint/test_fastquery.py +++ b/tests/tendermint/test_fastquery.py @@ -5,18 +5,19 @@ import pytest -from planetmint.common.transaction import TransactionLink -from planetmint.models import Transaction +from planetmint.transactions.common.transaction import TransactionLink +from planetmint.transactions.types.assets.create import Create +from planetmint.transactions.types.assets.transfer import Transfer pytestmark = pytest.mark.bdb @pytest.fixture def txns(b, user_pk, user_sk, user2_pk, user2_sk): - txs = [Transaction.create([user_pk], [([user2_pk], 1)]).sign([user_sk]), - Transaction.create([user2_pk], [([user_pk], 1)]).sign([user2_sk]), - Transaction.create([user_pk], [([user_pk], 1), ([user2_pk], 1)]) - .sign([user_sk])] + txs = [Create.generate([user_pk], [([user2_pk], 1)]).sign([user_sk]), + Create.generate([user2_pk], [([user_pk], 1)]).sign([user2_sk]), + Create.generate([user_pk], [([user_pk], 1), ([user2_pk], 1)]) + .sign([user_sk])] b.store_bulk_transactions(txs) return txs @@ -49,12 +50,12 @@ def test_get_outputs_by_public_key(b, user_pk, user2_pk, txns): def test_filter_spent_outputs(b, user_pk, user_sk): out = [([user_pk], 1)] - tx1 = Transaction.create([user_pk], out * 2) + tx1 = Create.generate([user_pk], out * 2) tx1.sign([user_sk]) inputs = tx1.to_inputs() - tx2 = Transaction.transfer([inputs[0]], out, tx1.id) + tx2 = Transfer.generate([inputs[0]], out, tx1.id) tx2.sign([user_sk]) # tx2 produces a new unspent. inputs[1] remains unspent. @@ -71,12 +72,12 @@ def test_filter_spent_outputs(b, user_pk, user_sk): def test_filter_unspent_outputs(b, user_pk, user_sk): out = [([user_pk], 1)] - tx1 = Transaction.create([user_pk], out * 2) + tx1 = Create.generate([user_pk], out * 2) tx1.sign([user_sk]) inputs = tx1.to_inputs() - tx2 = Transaction.transfer([inputs[0]], out, tx1.id) + tx2 = Transfer.generate([inputs[0]], out, tx1.id) tx2.sign([user_sk]) # tx2 produces a new unspent. input[1] remains unspent. @@ -95,13 +96,13 @@ def test_outputs_query_key_order(b, user_pk, user_sk, user2_pk, user2_sk): from planetmint.backend.connection import Connection from planetmint.backend import query - tx1 = Transaction.create([user_pk], - [([user_pk], 3), ([user_pk], 2), ([user_pk], 1)]) \ - .sign([user_sk]) + tx1 = Create.generate([user_pk], + [([user_pk], 3), ([user_pk], 2), ([user_pk], 1)])\ + .sign([user_sk]) b.store_bulk_transactions([tx1]) inputs = tx1.to_inputs() - tx2 = Transaction.transfer([inputs[1]], [([user2_pk], 2)], tx1.id).sign([user_sk]) + tx2 = Transfer.generate([inputs[1]], [([user2_pk], 2)], tx1.id).sign([user_sk]) assert tx2.validate(b) tx2_dict = tx2.to_dict() diff --git a/tests/tendermint/test_integration.py b/tests/tendermint/test_integration.py index 05cb69b..69c818d 100644 --- a/tests/tendermint/test_integration.py +++ b/tests/tendermint/test_integration.py @@ -4,28 +4,29 @@ # Code is Apache-2.0 and docs are CC-BY-4.0 import codecs +from planetmint.transactions.types.assets.create import Create +from planetmint.transactions.types.assets.transfer import Transfer -from abci import types_v0_31_5 as types +from tendermint.abci import types_pb2 as types import json import pytest from abci.server import ProtocolHandler -from abci.encoding import read_messages +from abci.utils import read_messages -from planetmint.common.transaction_mode_types import BROADCAST_TX_COMMIT, BROADCAST_TX_SYNC +from planetmint.transactions.common.transaction_mode_types import BROADCAST_TX_COMMIT, BROADCAST_TX_SYNC from planetmint.version import __tm_supported_versions__ from io import BytesIO @pytest.mark.bdb -def test_app(a, b, init_chain_request): +def test_app(b, eventqueue_fixture, init_chain_request): from planetmint import App from planetmint.tendermint_utils import calculate_hash - from planetmint.common.crypto import generate_key_pair - from planetmint.models import Transaction + from planetmint.transactions.common.crypto import generate_key_pair - app = App(a, b) + app = App(b, eventqueue_fixture) p = ProtocolHandler(app) data = p.process('info', @@ -42,14 +43,14 @@ def test_app(a, b, init_chain_request): assert block0['height'] == 0 assert block0['app_hash'] == '' - pk = codecs.encode(init_chain_request.validators[0].pub_key.data, 'base64').decode().strip('\n') + pk = codecs.encode(init_chain_request.validators[0].pub_key.ed25519, 'base64').decode().strip('\n') [validator] = b.get_validators(height=1) assert validator['public_key']['value'] == pk assert validator['voting_power'] == 10 alice = generate_key_pair() bob = generate_key_pair() - tx = Transaction.create([alice.public_key], + tx = Create.generate([alice.public_key], [([bob.public_key], 1)])\ .sign([alice.private_key]) etxn = json.dumps(tx.to_dict()).encode('utf8') @@ -113,12 +114,11 @@ def test_app(a, b, init_chain_request): @pytest.mark.abci def test_post_transaction_responses(tendermint_ws_url, b): - from planetmint.common.crypto import generate_key_pair - from planetmint.models import Transaction + from planetmint.transactions.common.crypto import generate_key_pair alice = generate_key_pair() bob = generate_key_pair() - tx = Transaction.create([alice.public_key], + tx = Create.generate([alice.public_key], [([alice.public_key], 1)], asset=None)\ .sign([alice.private_key]) @@ -126,7 +126,7 @@ def test_post_transaction_responses(tendermint_ws_url, b): code, message = b.write_transaction(tx, BROADCAST_TX_COMMIT) assert code == 202 - tx_transfer = Transaction.transfer(tx.to_inputs(), + tx_transfer = Transfer.generate(tx.to_inputs(), [([bob.public_key], 1)], asset_id=tx.id)\ .sign([alice.private_key]) @@ -135,7 +135,7 @@ def test_post_transaction_responses(tendermint_ws_url, b): assert code == 202 carly = generate_key_pair() - double_spend = Transaction.transfer( + double_spend = Transfer.generate( tx.to_inputs(), [([carly.public_key], 1)], asset_id=tx.id, @@ -144,14 +144,3 @@ def test_post_transaction_responses(tendermint_ws_url, b): code, message = b.write_transaction(double_spend, mode) assert code == 500 assert message == 'Transaction validation failed' - - -@pytest.mark.bdb -def test_exit_when_tm_ver_not_supported(a, b): - from planetmint import App - - app = App(a, b) - p = ProtocolHandler(app) - - with pytest.raises(SystemExit): - p.process('info', types.Request(info=types.RequestInfo(version='2'))) diff --git a/tests/tendermint/test_lib.py b/tests/tendermint/test_lib.py index 9953d1e..7f0df2e 100644 --- a/tests/tendermint/test_lib.py +++ b/tests/tendermint/test_lib.py @@ -6,6 +6,8 @@ from operator import index import os from unittest.mock import patch +from planetmint.transactions.types.assets.create import Create +from planetmint.transactions.types.assets.transfer import Transfer try: from hashlib import sha3_256 @@ -17,22 +19,22 @@ import pytest from pymongo import MongoClient from planetmint import backend -from planetmint.common.transaction_mode_types import (BROADCAST_TX_COMMIT, - BROADCAST_TX_ASYNC, - BROADCAST_TX_SYNC) +from planetmint.transactions.common.transaction_mode_types import ( + BROADCAST_TX_COMMIT, BROADCAST_TX_ASYNC, BROADCAST_TX_SYNC) from planetmint.lib import Block @pytest.mark.bdb def test_asset_is_separated_from_transaciton(b): import copy - from planetmint.models import Transaction - from planetmint.common.crypto import generate_key_pair + from planetmint.transactions.common.crypto import generate_key_pair from planetmint.backend.tarantool.connection import TarantoolDB + if isinstance(b.connection, TarantoolDB): pytest.skip("This specific function is skipped because, assets are stored differently if using Tarantool") + alice = generate_key_pair() bob = generate_key_pair() @@ -45,7 +47,7 @@ def test_asset_is_separated_from_transaciton(b): 'tell a lie', 'hurt you']} - tx = Transaction.create([alice.public_key], + tx = Create.generate([alice.public_key], [([bob.public_key], 1)], metadata=None, asset=asset) \ @@ -86,11 +88,10 @@ def test_get_empty_block(_0, _1, b): def test_validation_error(b): - from planetmint.models import Transaction - from planetmint.common.crypto import generate_key_pair + from planetmint.transactions.common.crypto import generate_key_pair alice = generate_key_pair() - tx = Transaction.create([alice.public_key], + tx = Create.generate([alice.public_key], [([alice.public_key], 1)], asset=None) \ .sign([alice.private_key]).to_dict() @@ -101,12 +102,11 @@ def test_validation_error(b): @patch('requests.post') def test_write_and_post_transaction(mock_post, b): - from planetmint.models import Transaction - from planetmint.common.crypto import generate_key_pair + from planetmint.transactions.common.crypto import generate_key_pair from planetmint.tendermint_utils import encode_transaction alice = generate_key_pair() - tx = Transaction.create([alice.public_key], + tx = Create.generate([alice.public_key], [([alice.public_key], 1)], asset=None) \ .sign([alice.private_key]).to_dict() @@ -128,10 +128,9 @@ def test_write_and_post_transaction(mock_post, b): BROADCAST_TX_COMMIT ]) def test_post_transaction_valid_modes(mock_post, b, mode): - from planetmint.models import Transaction - from planetmint.common.crypto import generate_key_pair + from planetmint.transactions.common.crypto import generate_key_pair alice = generate_key_pair() - tx = Transaction.create([alice.public_key], + tx = Create.generate([alice.public_key], [([alice.public_key], 1)], asset=None) \ .sign([alice.private_key]).to_dict() @@ -143,11 +142,10 @@ def test_post_transaction_valid_modes(mock_post, b, mode): def test_post_transaction_invalid_mode(b): - from planetmint.models import Transaction - from planetmint.common.crypto import generate_key_pair - from planetmint.common.exceptions import ValidationError + from planetmint.transactions.common.crypto import generate_key_pair + from planetmint.transactions.common.exceptions import ValidationError alice = generate_key_pair() - tx = Transaction.create([alice.public_key], + tx = Create.generate([alice.public_key], [([alice.public_key], 1)], asset=None) \ .sign([alice.private_key]).to_dict() @@ -409,28 +407,27 @@ def test_get_utxoset_merkle_root(b, utxoset): @pytest.mark.bdb def test_get_spent_transaction_critical_double_spend(b, alice, bob, carol): - from planetmint.models import Transaction from planetmint.exceptions import CriticalDoubleSpend - from planetmint.common.exceptions import DoubleSpend + from planetmint.transactions.common.exceptions import DoubleSpend asset = {'test': 'asset'} - tx = Transaction.create([alice.public_key], + tx = Create.generate([alice.public_key], [([alice.public_key], 1)], asset=asset) \ .sign([alice.private_key]) - tx_transfer = Transaction.transfer(tx.to_inputs(), + tx_transfer = Transfer.generate(tx.to_inputs(), [([bob.public_key], 1)], asset_id=tx.id) \ .sign([alice.private_key]) - double_spend = Transaction.transfer(tx.to_inputs(), + double_spend = Transfer.generate(tx.to_inputs(), [([carol.public_key], 1)], asset_id=tx.id) \ .sign([alice.private_key]) - same_input_double_spend = Transaction.transfer(tx.to_inputs() + tx.to_inputs(), + same_input_double_spend = Transfer.generate(tx.to_inputs() + tx.to_inputs(), [([bob.public_key], 1)], asset_id=tx.id) \ .sign([alice.private_key]) @@ -458,16 +455,15 @@ def test_get_spent_transaction_critical_double_spend(b, alice, bob, carol): def test_validation_with_transaction_buffer(b): - from planetmint.common.crypto import generate_key_pair - from planetmint.models import Transaction + from planetmint.transactions.common.crypto import generate_key_pair priv_key, pub_key = generate_key_pair() - create_tx = Transaction.create([pub_key], [([pub_key], 10)]).sign([priv_key]) - transfer_tx = Transaction.transfer(create_tx.to_inputs(), + create_tx = Create.generate([pub_key], [([pub_key], 10)]).sign([priv_key]) + transfer_tx = Transfer.generate(create_tx.to_inputs(), [([pub_key], 10)], asset_id=create_tx.id).sign([priv_key]) - double_spend = Transaction.transfer(create_tx.to_inputs(), + double_spend = Transfer.generate(create_tx.to_inputs(), [([pub_key], 10)], asset_id=create_tx.id).sign([priv_key]) @@ -514,21 +510,20 @@ def test_migrate_abci_chain_generates_new_chains(b, chain, block_height, @pytest.mark.bdb def test_get_spent_key_order(b, user_pk, user_sk, user2_pk, user2_sk): from planetmint import backend - from planetmint.models import Transaction - from planetmint.common.crypto import generate_key_pair - from planetmint.common.exceptions import DoubleSpend + from planetmint.transactions.common.crypto import generate_key_pair + from planetmint.transactions.common.exceptions import DoubleSpend alice = generate_key_pair() bob = generate_key_pair() - tx1 = Transaction.create([user_pk], + tx1 = Create.generate([user_pk], [([alice.public_key], 3), ([user_pk], 2)], asset=None) \ .sign([user_sk]) b.store_bulk_transactions([tx1]) assert tx1.validate(b) inputs = tx1.to_inputs() - tx2 = Transaction.transfer([inputs[1]], [([user2_pk], 2)], tx1.id).sign([user_sk]) + tx2 = Transfer.generate([inputs[1]], [([user2_pk], 2)], tx1.id).sign([user_sk]) assert tx2.validate(b) tx2_dict = tx2.to_dict() @@ -538,7 +533,7 @@ def test_get_spent_key_order(b, user_pk, user_sk, user2_pk, user2_sk): backend.query.store_transactions(b.connection, [tx2_dict]) - tx3 = Transaction.transfer([inputs[1]], [([bob.public_key], 2)], tx1.id).sign([user_sk]) + tx3 = Transfer.generate([inputs[1]], [([bob.public_key], 2)], tx1.id).sign([user_sk]) with pytest.raises(DoubleSpend): tx3.validate(b) diff --git a/tests/test_config_utils.py b/tests/test_config_utils.py index 3db0c12..c0ad1cb 100644 --- a/tests/test_config_utils.py +++ b/tests/test_config_utils.py @@ -131,108 +131,108 @@ def test_env_config(monkeypatch): def test_autoconfigure_read_both_from_file_and_env(monkeypatch, request): # TODO Disabled until we create a better config format return # constants - # DATABASE_HOST = 'test-host' - # DATABASE_NAME = 'test-dbname' - # DATABASE_PORT = 4242 - # DATABASE_BACKEND = request.config.getoption('--database-backend') - # SERVER_BIND = '1.2.3.4:56' - # WSSERVER_SCHEME = 'ws' - # WSSERVER_HOST = '1.2.3.4' - # WSSERVER_PORT = 57 - # WSSERVER_ADVERTISED_SCHEME = 'wss' - # WSSERVER_ADVERTISED_HOST = 'a.b.c.d' - # WSSERVER_ADVERTISED_PORT = 89 - # LOG_FILE = '/somewhere/something.log' - # - # file_config = { - # 'database': { - # 'host': DATABASE_HOST - # }, - # 'log': { - # 'level_console': 'debug', - # }, - # } - # - # monkeypatch.setattr('planetmint.config_utils.file_config', - # lambda *args, **kwargs: file_config) - # - # monkeypatch.setattr('os.environ', { - # 'PLANETMINT_DATABASE_NAME': DATABASE_NAME, - # 'PLANETMINT_DATABASE_PORT': str(DATABASE_PORT), - # 'PLANETMINT_DATABASE_BACKEND': DATABASE_BACKEND, - # 'PLANETMINT_SERVER_BIND': SERVER_BIND, - # 'PLANETMINT_WSSERVER_SCHEME': WSSERVER_SCHEME, - # 'PLANETMINT_WSSERVER_HOST': WSSERVER_HOST, - # 'PLANETMINT_WSSERVER_PORT': WSSERVER_PORT, - # 'PLANETMINT_WSSERVER_ADVERTISED_SCHEME': WSSERVER_ADVERTISED_SCHEME, - # 'PLANETMINT_WSSERVER_ADVERTISED_HOST': WSSERVER_ADVERTISED_HOST, - # 'PLANETMINT_WSSERVER_ADVERTISED_PORT': WSSERVER_ADVERTISED_PORT, - # 'PLANETMINT_LOG_FILE': LOG_FILE, - # 'PLANETMINT_LOG_FILE': LOG_FILE, - # 'PLANETMINT_DATABASE_CA_CERT': 'ca_cert', - # 'PLANETMINT_DATABASE_CRLFILE': 'crlfile', - # 'PLANETMINT_DATABASE_CERTFILE': 'certfile', - # 'PLANETMINT_DATABASE_KEYFILE': 'keyfile', - # 'PLANETMINT_DATABASE_KEYFILE_PASSPHRASE': 'passphrase', - # }) - # - # import planetmint - # from planetmint import config_utils - # from planetmint.log import DEFAULT_LOGGING_CONFIG as log_config - # config_utils.autoconfigure() - # - # database_mongodb = { - # 'backend': 'localmongodb', - # 'host': DATABASE_HOST, - # 'port': DATABASE_PORT, - # 'name': DATABASE_NAME, - # 'connection_timeout': 5000, - # 'max_tries': 3, - # 'replicaset': None, - # 'ssl': False, - # 'login': None, - # 'password': None, - # 'ca_cert': 'ca_cert', - # 'certfile': 'certfile', - # 'keyfile': 'keyfile', - # 'keyfile_passphrase': 'passphrase', - # 'crlfile': 'crlfile', - # } - # - # assert planetmint.config == { - # 'CONFIGURED': True, - # 'server': { - # 'bind': SERVER_BIND, - # 'loglevel': 'info', - # 'workers': None, - # }, - # 'wsserver': { - # 'scheme': WSSERVER_SCHEME, - # 'host': WSSERVER_HOST, - # 'port': WSSERVER_PORT, - # 'advertised_scheme': WSSERVER_ADVERTISED_SCHEME, - # 'advertised_host': WSSERVER_ADVERTISED_HOST, - # 'advertised_port': WSSERVER_ADVERTISED_PORT, - # }, - # 'database': database_mongodb, - # 'tendermint': { - # 'host': 'localhost', - # 'port': 26657, - # 'version': 'v0.31.5' - # }, - # 'log': { - # 'file': LOG_FILE, - # 'level_console': 'debug', - # 'error_file': log_config['handlers']['errors']['filename'], - # 'level_console': 'debug', - # 'level_logfile': 'info', - # 'datefmt_console': log_config['formatters']['console']['datefmt'], - # 'datefmt_logfile': log_config['formatters']['file']['datefmt'], - # 'fmt_console': log_config['formatters']['console']['format'], - # 'fmt_logfile': log_config['formatters']['file']['format'], - # 'granular_levels': {}, - # }, - # } + DATABASE_HOST = 'test-host' + DATABASE_NAME = 'test-dbname' + DATABASE_PORT = 4242 + DATABASE_BACKEND = request.config.getoption('--database-backend') + SERVER_BIND = '1.2.3.4:56' + WSSERVER_SCHEME = 'ws' + WSSERVER_HOST = '1.2.3.4' + WSSERVER_PORT = 57 + WSSERVER_ADVERTISED_SCHEME = 'wss' + WSSERVER_ADVERTISED_HOST = 'a.b.c.d' + WSSERVER_ADVERTISED_PORT = 89 + LOG_FILE = '/somewhere/something.log' + + file_config = { + 'database': { + 'host': DATABASE_HOST + }, + 'log': { + 'level_console': 'debug', + }, + } + + monkeypatch.setattr('planetmint.config_utils.file_config', + lambda *args, **kwargs: file_config) + + monkeypatch.setattr('os.environ', { + 'PLANETMINT_DATABASE_NAME': DATABASE_NAME, + 'PLANETMINT_DATABASE_PORT': str(DATABASE_PORT), + 'PLANETMINT_DATABASE_BACKEND': DATABASE_BACKEND, + 'PLANETMINT_SERVER_BIND': SERVER_BIND, + 'PLANETMINT_WSSERVER_SCHEME': WSSERVER_SCHEME, + 'PLANETMINT_WSSERVER_HOST': WSSERVER_HOST, + 'PLANETMINT_WSSERVER_PORT': WSSERVER_PORT, + 'PLANETMINT_WSSERVER_ADVERTISED_SCHEME': WSSERVER_ADVERTISED_SCHEME, + 'PLANETMINT_WSSERVER_ADVERTISED_HOST': WSSERVER_ADVERTISED_HOST, + 'PLANETMINT_WSSERVER_ADVERTISED_PORT': WSSERVER_ADVERTISED_PORT, + 'PLANETMINT_LOG_FILE': LOG_FILE, + 'PLANETMINT_LOG_FILE': LOG_FILE, + 'PLANETMINT_DATABASE_CA_CERT': 'ca_cert', + 'PLANETMINT_DATABASE_CRLFILE': 'crlfile', + 'PLANETMINT_DATABASE_CERTFILE': 'certfile', + 'PLANETMINT_DATABASE_KEYFILE': 'keyfile', + 'PLANETMINT_DATABASE_KEYFILE_PASSPHRASE': 'passphrase', + }) + + import planetmint + from planetmint import config_utils + from planetmint.log import DEFAULT_LOGGING_CONFIG as log_config + config_utils.autoconfigure() + + database_mongodb = { + 'backend': 'localmongodb', + 'host': DATABASE_HOST, + 'port': DATABASE_PORT, + 'name': DATABASE_NAME, + 'connection_timeout': 5000, + 'max_tries': 3, + 'replicaset': None, + 'ssl': False, + 'login': None, + 'password': None, + 'ca_cert': 'ca_cert', + 'certfile': 'certfile', + 'keyfile': 'keyfile', + 'keyfile_passphrase': 'passphrase', + 'crlfile': 'crlfile', + } + + assert planetmint.config == { + 'CONFIGURED': True, + 'server': { + 'bind': SERVER_BIND, + 'loglevel': 'info', + 'workers': None, + }, + 'wsserver': { + 'scheme': WSSERVER_SCHEME, + 'host': WSSERVER_HOST, + 'port': WSSERVER_PORT, + 'advertised_scheme': WSSERVER_ADVERTISED_SCHEME, + 'advertised_host': WSSERVER_ADVERTISED_HOST, + 'advertised_port': WSSERVER_ADVERTISED_PORT, + }, + 'database': database_mongodb, + 'tendermint': { + 'host': 'localhost', + 'port': 26657, + 'version': 'v0.34.15' + }, + 'log': { + 'file': LOG_FILE, + 'level_console': 'debug', + 'error_file': log_config['handlers']['errors']['filename'], + 'level_console': 'debug', + 'level_logfile': 'info', + 'datefmt_console': log_config['formatters']['console']['datefmt'], + 'datefmt_logfile': log_config['formatters']['file']['datefmt'], + 'fmt_console': log_config['formatters']['console']['format'], + 'fmt_logfile': log_config['formatters']['file']['format'], + 'granular_levels': {}, + }, + } def test_autoconfigure_env_precedence(monkeypatch): @@ -294,7 +294,7 @@ def test_file_config(): def test_invalid_file_config(): from planetmint.config_utils import file_config - from planetmint.common import exceptions + from planetmint.transactions.common import exceptions with patch('builtins.open', mock_open(read_data='{_INVALID_JSON_}')): with pytest.raises(exceptions.ConfigurationError): file_config() diff --git a/tests/test_core.py b/tests/test_core.py index 2a892cf..ab7d3f4 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1,10 +1,16 @@ -# Copyright © 2020 Interplanetary Database Association e.V., +# Copyright � 2020 Interplanetary Database Association e.V., # Planetmint and IPDB software contributors. # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) # Code is Apache-2.0 and docs are CC-BY-4.0 +import json +from planetmint.transactions.types.assets.create import Create +from planetmint.transactions.types.assets.transfer import Transfer import pytest +import random +from tendermint.abci import types_pb2 as types +from tendermint.crypto import keys_pb2 @pytest.fixture def config(request, monkeypatch): @@ -15,7 +21,7 @@ def config(request, monkeypatch): config = { 'database': { 'backend': backend, - 'host': 'localhost', + 'host': 'tarantool', 'port': 3303, 'name': 'bigchain', 'replicaset': 'bigchain-rs', @@ -29,12 +35,10 @@ def config(request, monkeypatch): }, 'CONFIGURED': True, } - + monkeypatch.setattr('planetmint.config', config) - return config - def test_bigchain_class_default_initialization(config): from planetmint import Planetmint from planetmint.validation import BaseValidationRules @@ -46,7 +50,6 @@ def test_bigchain_class_default_initialization(config): #assert planet.connection.dbname == config['database']['name'] assert planet.validation == BaseValidationRules - def test_bigchain_class_initialization_with_parameters(): from planetmint import Planetmint from planetmint.backend import Connection @@ -70,7 +73,7 @@ def test_bigchain_class_initialization_with_parameters(): def test_get_spent_issue_1271(b, alice, bob, carol): from planetmint.models import Transaction - tx_1 = Transaction.create( + tx_1 = Create.generate( [carol.public_key], [([carol.public_key], 8)], ).sign([carol.private_key]) @@ -79,7 +82,7 @@ def test_get_spent_issue_1271(b, alice, bob, carol): assert tx_1.validate(b) b.store_bulk_transactions([tx_1]) - tx_2 = Transaction.transfer( + tx_2 = Transfer.generate( tx_1.to_inputs(), [([bob.public_key], 2), ([alice.public_key], 2), @@ -91,7 +94,7 @@ def test_get_spent_issue_1271(b, alice, bob, carol): assert tx_2.validate(b) b.store_bulk_transactions([tx_2]) - tx_3 = Transaction.transfer( + tx_3 = Transfer.generate( tx_2.to_inputs()[2:3], [([alice.public_key], 1), ([carol.public_key], 3)], @@ -102,7 +105,7 @@ def test_get_spent_issue_1271(b, alice, bob, carol): assert tx_3.validate(b) b.store_bulk_transactions([tx_3]) - tx_4 = Transaction.transfer( + tx_4 = Transfer.generate( tx_2.to_inputs()[1:2] + tx_3.to_inputs()[0:1], [([bob.public_key], 3)], asset_id=tx_1.id, @@ -110,7 +113,7 @@ def test_get_spent_issue_1271(b, alice, bob, carol): assert tx_4.validate(b) b.store_bulk_transactions([tx_4]) - tx_5 = Transaction.transfer( + tx_5 = Transfer.generate( tx_2.to_inputs()[0:1], [([alice.public_key], 2)], asset_id=tx_1.id, @@ -122,3 +125,533 @@ def test_get_spent_issue_1271(b, alice, bob, carol): assert not b.get_spent(tx_5.id, 0) assert b.get_outputs_filtered(alice.public_key) assert b.get_outputs_filtered(alice.public_key, spent=False) + +# from planetmint import App +# from planetmint.backend.localmongodb import query +# from planetmint.transactions.common.crypto import generate_key_pair +# from planetmint.core import (OkCode, +# CodeTypeError, +# rollback) +# from planetmint.transactions.types.elections.election import Election +# from planetmint.lib import Block +# from planetmint.migrations.chain_migration_election import ChainMigrationElection +# from planetmint.upsert_validator.validator_election import ValidatorElection +# from planetmint.upsert_validator.validator_utils import new_validator_set +# from planetmint.tendermint_utils import public_key_to_base64 +# from planetmint.version import __tm_supported_versions__ + +# from tests.utils import generate_election, generate_validators + + +# pytestmark = pytest.mark.bdb + + +# def encode_tx_to_bytes(transaction): +# return json.dumps(transaction.to_dict()).encode('utf8') + + +# def generate_address(): +# return ''.join(random.choices('1,2,3,4,5,6,7,8,9,A,B,C,D,E,F'.split(','), +# k=40)).encode() + + +# def generate_validator(): +# pk, _ = generate_key_pair() +# pub_key = keys_pb2.PublicKey(ed25519=pk.encode()) +# val = types.ValidatorUpdate(power=10, pub_key=pub_key) +# return val + + +# def generate_init_chain_request(chain_id, vals=None): +# vals = vals if vals is not None else [generate_validator()] +# return types.RequestInitChain(validators=vals, chain_id=chain_id) + + +# def test_init_chain_successfully_registers_chain(b): +# request = generate_init_chain_request('chain-XYZ') +# res = App(b).init_chain(request) +# assert res == types.ResponseInitChain() +# chain = query.get_latest_abci_chain(b.connection) +# assert chain == {'height': 0, 'chain_id': 'chain-XYZ', 'is_synced': True} +# assert query.get_latest_block(b.connection) == { +# 'height': 0, +# 'app_hash': '', +# 'transactions': [], +# } + + +# def test_init_chain_ignores_invalid_init_chain_requests(b): +# validators = [generate_validator()] +# request = generate_init_chain_request('chain-XYZ', validators) +# res = App(b).init_chain(request) +# assert res == types.ResponseInitChain() + +# validator_set = query.get_validator_set(b.connection) + +# invalid_requests = [ +# request, # the same request again +# # different validator set +# generate_init_chain_request('chain-XYZ'), +# # different chain ID +# generate_init_chain_request('chain-ABC', validators), +# ] +# for r in invalid_requests: +# with pytest.raises(SystemExit): +# App(b).init_chain(r) +# # assert nothing changed - neither validator set, nor chain ID +# new_validator_set = query.get_validator_set(b.connection) +# assert new_validator_set == validator_set +# new_chain_id = query.get_latest_abci_chain(b.connection)['chain_id'] +# assert new_chain_id == 'chain-XYZ' +# assert query.get_latest_block(b.connection) == { +# 'height': 0, +# 'app_hash': '', +# 'transactions': [], +# } + + +# def test_init_chain_recognizes_new_chain_after_migration(b): +# validators = [generate_validator()] +# request = generate_init_chain_request('chain-XYZ', validators) +# res = App(b).init_chain(request) +# assert res == types.ResponseInitChain() + + +# validator_set = query.get_validator_set(b.connection)['validators'] + +# # simulate a migration +# query.store_block(b.connection, Block(app_hash='', height=1, +# transactions=[])._asdict()) +# b.migrate_abci_chain() + + +# # the same or other mismatching requests are ignored +# invalid_requests = [ +# request, +# generate_init_chain_request('unknown', validators), +# generate_init_chain_request('chain-XYZ'), +# generate_init_chain_request('chain-XYZ-migrated-at-height-1'), +# ] +# for r in invalid_requests: +# with pytest.raises(SystemExit): +# App(b).init_chain(r) +# assert query.get_latest_abci_chain(b.connection) == { +# 'chain_id': 'chain-XYZ-migrated-at-height-1', +# 'is_synced': False, +# 'height': 2, +# } +# new_validator_set = query.get_validator_set(b.connection)['validators'] +# assert new_validator_set == validator_set + +# # a request with the matching chain ID and matching validator set +# # completes the migration +# request = generate_init_chain_request('chain-XYZ-migrated-at-height-1', +# validators) +# res = App(b).init_chain(request) +# assert res == types.ResponseInitChain() +# assert query.get_latest_abci_chain(b.connection) == { +# 'chain_id': 'chain-XYZ-migrated-at-height-1', +# 'is_synced': True, +# 'height': 2, +# } +# assert query.get_latest_block(b.connection) == { +# 'height': 2, +# 'app_hash': '', +# 'transactions': [], +# } + +# # requests with old chain ID and other requests are ignored +# invalid_requests = [ +# request, +# generate_init_chain_request('chain-XYZ', validators), +# generate_init_chain_request('chain-XYZ-migrated-at-height-1'), +# ] +# for r in invalid_requests: +# with pytest.raises(SystemExit): +# App(b).init_chain(r) +# assert query.get_latest_abci_chain(b.connection) == { +# 'chain_id': 'chain-XYZ-migrated-at-height-1', +# 'is_synced': True, +# 'height': 2, +# } +# new_validator_set = query.get_validator_set(b.connection)['validators'] +# assert new_validator_set == validator_set +# assert query.get_latest_block(b.connection) == { +# 'height': 2, +# 'app_hash': '', +# 'transactions': [], +# } + + +# def test_info(b): +# r = types.RequestInfo(version=__tm_supported_versions__[0]) +# app = App(b) + +# res = app.info(r) +# assert res.last_block_height == 0 +# assert res.last_block_app_hash == b'' + +# b.store_block(Block(app_hash='1', height=1, transactions=[])._asdict()) +# res = app.info(r) +# assert res.last_block_height == 1 +# assert res.last_block_app_hash == b'1' + +# # simulate a migration and assert the height is shifted +# b.store_abci_chain(2, 'chain-XYZ') +# app = App(b) +# b.store_block(Block(app_hash='2', height=2, transactions=[])._asdict()) +# res = app.info(r) +# assert res.last_block_height == 0 +# assert res.last_block_app_hash == b'2' + +# b.store_block(Block(app_hash='3', height=3, transactions=[])._asdict()) +# res = app.info(r) +# assert res.last_block_height == 1 +# assert res.last_block_app_hash == b'3' + +# # it's always the latest migration that is taken into account +# b.store_abci_chain(4, 'chain-XYZ-new') +# app = App(b) +# b.store_block(Block(app_hash='4', height=4, transactions=[])._asdict()) +# res = app.info(r) +# assert res.last_block_height == 0 +# assert res.last_block_app_hash == b'4' + + +# def test_check_tx__signed_create_is_ok(b): +# from planetmint import App +# from planetmint.transactions.common.crypto import generate_key_pair + +# alice = generate_key_pair() +# bob = generate_key_pair() + +# tx = Create.generate([alice.public_key], +# [([bob.public_key], 1)])\ +# .sign([alice.private_key]) + +# app = App(b) +# result = app.check_tx(encode_tx_to_bytes(tx)) +# assert result.code == OkCode + + +# def test_check_tx__unsigned_create_is_error(b): +# from planetmint import App +# from planetmint.transactions.common.crypto import generate_key_pair + +# alice = generate_key_pair() +# bob = generate_key_pair() + +# tx = Create.generate([alice.public_key], +# [([bob.public_key], 1)]) + +# app = App(b) +# result = app.check_tx(encode_tx_to_bytes(tx)) +# assert result.code == CodeTypeError + + +# def test_deliver_tx__valid_create_updates_db_and_emits_event(b, init_chain_request): +# import multiprocessing as mp +# from planetmint import App +# from planetmint.transactions.common.crypto import generate_key_pair + +# alice = generate_key_pair() +# bob = generate_key_pair() +# events = mp.Queue() + +# tx = Create.generate([alice.public_key], +# [([bob.public_key], 1)])\ +# .sign([alice.private_key]) + +# app = App(b, events) + +# app.init_chain(init_chain_request) + +# begin_block = types.RequestBeginBlock() +# app.begin_block(begin_block) + +# result = app.deliver_tx(encode_tx_to_bytes(tx)) +# assert result.code == OkCode + +# app.end_block(types.RequestEndBlock(height=99)) +# app.commit() +# assert b.get_transaction(tx.id).id == tx.id +# block_event = events.get() +# assert block_event.data['transactions'] == [tx] + +# # unspent_outputs = b.get_unspent_outputs() +# # unspent_output = next(unspent_outputs) +# # expected_unspent_output = next(tx.unspent_outputs)._asdict() +# # assert unspent_output == expected_unspent_output +# # with pytest.raises(StopIteration): +# # next(unspent_outputs) + + +# def test_deliver_tx__double_spend_fails(b, eventqueue_fixture, init_chain_request): +# from planetmint import App +# from planetmint.transactions.common.crypto import generate_key_pair + +# alice = generate_key_pair() +# bob = generate_key_pair() + +# tx = Create.generate([alice.public_key], +# [([bob.public_key], 1)])\ +# .sign([alice.private_key]) + +# app = App(b, eventqueue_fixture) +# app.init_chain(init_chain_request) + +# begin_block = types.RequestBeginBlock() +# app.begin_block(begin_block) + +# result = app.deliver_tx(encode_tx_to_bytes(tx)) +# assert result.code == OkCode + +# app.end_block(types.RequestEndBlock(height=99)) +# app.commit() + +# assert b.get_transaction(tx.id).id == tx.id +# result = app.deliver_tx(encode_tx_to_bytes(tx)) +# assert result.code == CodeTypeError + + +# def test_deliver_transfer_tx__double_spend_fails(b, init_chain_request): +# from planetmint import App +# from planetmint.transactions.common.crypto import generate_key_pair + +# app = App(b) +# app.init_chain(init_chain_request) + +# begin_block = types.RequestBeginBlock() +# app.begin_block(begin_block) + +# alice = generate_key_pair() +# bob = generate_key_pair() +# carly = generate_key_pair() + +# asset = { +# 'msg': 'live long and prosper' +# } + +# tx = Create.generate([alice.public_key], +# [([alice.public_key], 1)], +# asset=asset)\ +# .sign([alice.private_key]) + +# result = app.deliver_tx(encode_tx_to_bytes(tx)) +# assert result.code == OkCode + +# tx_transfer = Transfer.generate(tx.to_inputs(), +# [([bob.public_key], 1)], +# asset_id=tx.id)\ +# .sign([alice.private_key]) + +# result = app.deliver_tx(encode_tx_to_bytes(tx_transfer)) +# assert result.code == OkCode + +# double_spend = Transfer.generate(tx.to_inputs(), +# [([carly.public_key], 1)], +# asset_id=tx.id)\ +# .sign([alice.private_key]) + +# result = app.deliver_tx(encode_tx_to_bytes(double_spend)) +# assert result.code == CodeTypeError + + +# def test_end_block_return_validator_updates(b, init_chain_request): +# app = App(b) +# app.init_chain(init_chain_request) + +# begin_block = types.RequestBeginBlock() +# app.begin_block(begin_block) + +# # generate a block containing a concluded validator election +# validators = generate_validators([1] * 4) +# b.store_validator_set(1, [v['storage'] for v in validators]) + +# new_validator = generate_validators([1])[0] + +# public_key = validators[0]['public_key'] +# private_key = validators[0]['private_key'] +# voter_keys = [v['private_key'] for v in validators] + +# election, votes = generate_election(b, +# ValidatorElection, +# public_key, private_key, +# new_validator['election'], +# voter_keys) +# b.store_block(Block(height=1, transactions=[election.id], +# app_hash='')._asdict()) +# b.store_bulk_transactions([election]) +# Election.process_block(b, 1, [election]) + +# app.block_transactions = votes + +# resp = app.end_block(types.RequestEndBlock(height=2)) +# assert resp.validator_updates[0].power == new_validator['election']['power'] +# expected = bytes.fromhex(new_validator['election']['public_key']['value']) +# assert expected == resp.validator_updates[0].pub_key.ed25519 + + +# def test_store_pre_commit_state_in_end_block(b, alice, init_chain_request): +# from planetmint import App +# from planetmint.backend import query + +# tx = Create.generate([alice.public_key], +# [([alice.public_key], 1)], +# asset={'msg': 'live long and prosper'})\ +# .sign([alice.private_key]) + +# app = App(b) +# app.init_chain(init_chain_request) + +# begin_block = types.RequestBeginBlock() +# app.begin_block(begin_block) +# app.deliver_tx(encode_tx_to_bytes(tx)) +# app.end_block(types.RequestEndBlock(height=99)) + +# resp = query.get_pre_commit_state(b.connection) +# assert resp['height'] == 99 +# assert resp['transactions'] == [tx.id] + +# app.begin_block(begin_block) +# app.deliver_tx(encode_tx_to_bytes(tx)) +# app.end_block(types.RequestEndBlock(height=100)) +# resp = query.get_pre_commit_state(b.connection) +# assert resp['height'] == 100 +# assert resp['transactions'] == [tx.id] + +# # simulate a chain migration and assert the height is shifted +# b.store_abci_chain(100, 'new-chain') +# app = App(b) +# app.begin_block(begin_block) +# app.deliver_tx(encode_tx_to_bytes(tx)) +# app.end_block(types.RequestEndBlock(height=1)) +# resp = query.get_pre_commit_state(b.connection) +# assert resp['height'] == 101 +# assert resp['transactions'] == [tx.id] + + +# def test_rollback_pre_commit_state_after_crash(b): +# validators = generate_validators([1] * 4) +# b.store_validator_set(1, [v['storage'] for v in validators]) +# b.store_block(Block(height=1, transactions=[], app_hash='')._asdict()) + +# public_key = validators[0]['public_key'] +# private_key = validators[0]['private_key'] +# voter_keys = [v['private_key'] for v in validators] + +# migration_election, votes = generate_election(b, +# ChainMigrationElection, +# public_key, private_key, +# {}, +# voter_keys) + +# total_votes = votes +# txs = [migration_election, *votes] + +# new_validator = generate_validators([1])[0] +# validator_election, votes = generate_election(b, +# ValidatorElection, +# public_key, private_key, +# new_validator['election'], +# voter_keys) + +# total_votes += votes +# txs += [validator_election, *votes] + +# b.store_bulk_transactions(txs) +# b.store_abci_chain(2, 'new_chain') +# b.store_validator_set(2, [v['storage'] for v in validators]) +# # TODO change to `4` when upgrading to Tendermint 0.22.4. +# b.store_validator_set(3, [new_validator['storage']]) +# b.store_election(migration_election.id, 2, is_concluded=False) +# b.store_election(validator_election.id, 2, is_concluded=True) + +# # no pre-commit state +# rollback(b) + +# for tx in txs: +# assert b.get_transaction(tx.id) +# assert b.get_latest_abci_chain() +# assert len(b.get_validator_change()['validators']) == 1 +# assert b.get_election(migration_election.id) +# assert b.get_election(validator_election.id) + +# b.store_pre_commit_state({'height': 2, 'transactions': [tx.id for tx in txs]}) + +# rollback(b) + +# for tx in txs: +# assert not b.get_transaction(tx.id) +# assert not b.get_latest_abci_chain() +# assert len(b.get_validator_change()['validators']) == 4 +# assert len(b.get_validator_change(2)['validators']) == 4 +# assert not b.get_election(migration_election.id) +# assert not b.get_election(validator_election.id) + + +# def test_new_validator_set(b): +# node1 = {'public_key': {'type': 'ed25519-base64', +# 'value': 'FxjS2/8AFYoIUqF6AcePTc87qOT7e4WGgH+sGCpTUDQ='}, +# 'voting_power': 10} +# node1_new_power = {'public_key': {'value': '1718D2DBFF00158A0852A17A01C78F4DCF3BA8E4FB7B8586807FAC182A535034', +# 'type': 'ed25519-base16'}, +# 'power': 20} +# node2 = {'public_key': {'value': '1888A353B181715CA2554701D06C1665BC42C5D936C55EA9C5DBCBDB8B3F02A3', +# 'type': 'ed25519-base16'}, +# 'power': 10} + +# validators = [node1] +# updates = [node1_new_power, node2] +# b.store_validator_set(1, validators) +# updated_validator_set = new_validator_set(b.get_validators(1), updates) + +# updated_validators = [] +# for u in updates: +# updated_validators.append({'public_key': {'type': 'ed25519-base64', +# 'value': public_key_to_base64(u['public_key']['value'])}, +# 'voting_power': u['power']}) + +# assert updated_validator_set == updated_validators + + +# def test_info_aborts_if_chain_is_not_synced(b): +# b.store_abci_chain(0, 'chain-XYZ', False) + +# with pytest.raises(SystemExit): +# App(b).info(types.RequestInfo()) + + +# def test_check_tx_aborts_if_chain_is_not_synced(b): +# b.store_abci_chain(0, 'chain-XYZ', False) + +# with pytest.raises(SystemExit): +# App(b).check_tx('some bytes') + + +# def test_begin_aborts_if_chain_is_not_synced(b): +# b.store_abci_chain(0, 'chain-XYZ', False) + +# with pytest.raises(SystemExit): +# App(b).info(types.RequestBeginBlock()) + + +# def test_deliver_tx_aborts_if_chain_is_not_synced(b): +# b.store_abci_chain(0, 'chain-XYZ', False) + +# with pytest.raises(SystemExit): +# App(b).deliver_tx('some bytes') + + +# def test_end_block_aborts_if_chain_is_not_synced(b): +# b.store_abci_chain(0, 'chain-XYZ', False) + +# with pytest.raises(SystemExit): +# App(b).info(types.RequestEndBlock()) + + +# def test_commit_aborts_if_chain_is_not_synced(b): +# b.store_abci_chain(0, 'chain-XYZ', False) + +# with pytest.raises(SystemExit): +# App(b).commit() diff --git a/tests/test_parallel_validation.py b/tests/test_parallel_validation.py index a0aec26..29e8864 100644 --- a/tests/test_parallel_validation.py +++ b/tests/test_parallel_validation.py @@ -5,9 +5,9 @@ import pytest -from planetmint.common.crypto import generate_key_pair -from planetmint.models import Transaction - +from planetmint.transactions.common.crypto import generate_key_pair +from planetmint.transactions.types.assets.create import Create +from planetmint.transactions.types.assets.transfer import Transfer pytestmark = pytest.mark.tendermint @@ -16,8 +16,8 @@ def generate_create_and_transfer(keypair=None): if not keypair: keypair = generate_key_pair() priv_key, pub_key = keypair - create_tx = Transaction.create([pub_key], [([pub_key], 10)]).sign([priv_key]) - transfer_tx = Transaction.transfer( + create_tx = Create.generate([pub_key], [([pub_key], 10)]).sign([priv_key]) + transfer_tx = Transfer.generate( create_tx.to_inputs(), [([pub_key], 10)], asset_id=create_tx.id).sign([priv_key]) @@ -30,7 +30,7 @@ def test_validation_worker_process_multiple_transactions(b): keypair = generate_key_pair() create_tx, transfer_tx = generate_create_and_transfer(keypair) - double_spend = Transaction.transfer( + double_spend = Transfer.generate( create_tx.to_inputs(), [([keypair.public_key], 10)], asset_id=create_tx.id).sign([keypair.private_key]) diff --git a/tests/test_txlist.py b/tests/test_txlist.py index bd5feb4..8475be2 100644 --- a/tests/test_txlist.py +++ b/tests/test_txlist.py @@ -12,18 +12,19 @@ import pytest @pytest.fixture def txlist(b, user_pk, user2_pk, user_sk, user2_sk): - from planetmint.models import Transaction + from planetmint.transactions.types.assets.create import Create + from planetmint.transactions.types.assets.transfer import Transfer # Create two CREATE transactions - create1 = Transaction.create([user_pk], [([user2_pk], 6)]) - create1 = create1.sign([user_sk]) + create1 = Create.generate([user_pk], [([user2_pk], 6)]) \ + .sign([user_sk]) - create2 = Transaction.create([user2_pk], + create2 = Create.generate([user2_pk], [([user2_pk], 5), ([user_pk], 5)]) \ .sign([user2_sk]) # Create a TRANSFER transactions - transfer1 = Transaction.transfer(create1.to_inputs(), + transfer1 = Transfer.generate(create1.to_inputs(), [([user_pk], 8)], create1.id).sign([user2_sk]) diff --git a/tests/upsert_validator/conftest.py b/tests/upsert_validator/conftest.py index cd0d1a3..976b996 100644 --- a/tests/upsert_validator/conftest.py +++ b/tests/upsert_validator/conftest.py @@ -19,7 +19,7 @@ def valid_upsert_validator_election_b(b, node_key, new_validator): @pytest.fixture -@patch('planetmint.elections.election.uuid4', lambda: 'mock_uuid4') +@patch('planetmint.transactions.types.elections.election.uuid4', lambda: 'mock_uuid4') def fixed_seed_election(b_mock, node_key, new_validator): voters = ValidatorElection.recipients(b_mock) return ValidatorElection.generate([node_key.public_key], diff --git a/tests/upsert_validator/test_upsert_validator_vote.py b/tests/upsert_validator/test_upsert_validator_vote.py index d2b950c..95ec43c 100644 --- a/tests/upsert_validator/test_upsert_validator_vote.py +++ b/tests/upsert_validator/test_upsert_validator_vote.py @@ -6,14 +6,14 @@ import pytest import codecs -from planetmint.elections.election import Election +from planetmint.transactions.types.elections.election import Election from planetmint.tendermint_utils import public_key_to_base64 from planetmint.upsert_validator import ValidatorElection -from planetmint.common.exceptions import AmountError -from planetmint.common.crypto import generate_key_pair -from planetmint.common.exceptions import ValidationError -from planetmint.common.transaction_mode_types import BROADCAST_TX_COMMIT -from planetmint.elections.vote import Vote +from planetmint.transactions.common.exceptions import AmountError +from planetmint.transactions.common.crypto import generate_key_pair +from planetmint.transactions.common.exceptions import ValidationError +from planetmint.transactions.common.transaction_mode_types import BROADCAST_TX_COMMIT +from planetmint.transactions.types.elections.vote import Vote from tests.utils import generate_block, gen_vote pytestmark = [pytest.mark.execute] @@ -296,7 +296,7 @@ def test_get_validator_update(b, node_keys, node_key, ed25519_node_keys): update = Election.process_block(b, 4, [tx_vote0, tx_vote1, tx_vote2]) assert len(update) == 1 - update_public_key = codecs.encode(update[0].pub_key.data, 'base64').decode().rstrip('\n') + update_public_key = codecs.encode(update[0].pub_key.ed25519, 'base64').decode().rstrip('\n') assert update_public_key == public_key64 # remove validator @@ -319,7 +319,7 @@ def test_get_validator_update(b, node_keys, node_key, ed25519_node_keys): update = Election.process_block(b, 9, [tx_vote2]) assert len(update) == 1 - update_public_key = codecs.encode(update[0].pub_key.data, 'base64').decode().rstrip('\n') + update_public_key = codecs.encode(update[0].pub_key.ed25519, 'base64').decode().rstrip('\n') assert update_public_key == public_key64 # assert that the public key is not a part of the current validator set diff --git a/tests/upsert_validator/test_validator_election.py b/tests/upsert_validator/test_validator_election.py index 3b8e3e5..77aaf1c 100644 --- a/tests/upsert_validator/test_validator_election.py +++ b/tests/upsert_validator/test_validator_election.py @@ -9,11 +9,9 @@ import pytest from planetmint.tendermint_utils import public_key_to_base64 from planetmint.upsert_validator import ValidatorElection -from planetmint.common.exceptions import (DuplicateTransaction, - UnequalValidatorSet, - InvalidProposer, - MultipleInputsError, - InvalidPowerChange) +from planetmint.transactions.common.exceptions import ( + DuplicateTransaction, UnequalValidatorSet, InvalidProposer, + MultipleInputsError, InvalidPowerChange) pytestmark = pytest.mark.bdb @@ -27,7 +25,7 @@ def test_upsert_validator_valid_election(b_mock, new_validator, node_key): def test_upsert_validator_invalid_election_public_key(b_mock, new_validator, node_key): - from planetmint.common.exceptions import InvalidPublicKey + from planetmint.transactions.common.exceptions import InvalidPublicKey for iv in ['ed25519-base32', 'ed25519-base64']: new_validator['public_key']['type'] = iv @@ -51,7 +49,7 @@ def test_upsert_validator_invalid_power_election(b_mock, new_validator, node_key def test_upsert_validator_invalid_proposed_election(b_mock, new_validator, node_key): - from planetmint.common.crypto import generate_key_pair + from planetmint.transactions.common.crypto import generate_key_pair alice = generate_key_pair() voters = ValidatorElection.recipients(b_mock) @@ -63,7 +61,7 @@ def test_upsert_validator_invalid_proposed_election(b_mock, new_validator, node_ def test_upsert_validator_invalid_inputs_election(b_mock, new_validator, node_key): - from planetmint.common.crypto import generate_key_pair + from planetmint.transactions.common.crypto import generate_key_pair alice = generate_key_pair() voters = ValidatorElection.recipients(b_mock) @@ -74,7 +72,7 @@ def test_upsert_validator_invalid_inputs_election(b_mock, new_validator, node_ke election.validate(b_mock) -@patch('planetmint.elections.election.uuid4', lambda: 'mock_uuid4') +@patch('planetmint.transactions.types.elections.election.uuid4', lambda: 'mock_uuid4') def test_upsert_validator_invalid_election(b_mock, new_validator, node_key, fixed_seed_election): voters = ValidatorElection.recipients(b_mock) duplicate_election = ValidatorElection.generate([node_key.public_key], diff --git a/tests/utils.py b/tests/utils.py index 05a54e2..ceffff0 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -11,9 +11,10 @@ from functools import singledispatch from planetmint.backend.localmongodb.connection import LocalMongoDBConnection from planetmint.backend.schema import TABLES -from planetmint.common import crypto -from planetmint.common.transaction_mode_types import BROADCAST_TX_COMMIT -from planetmint.elections.election import Election, Vote +from planetmint.transactions.common import crypto +from planetmint.transactions.common.transaction_mode_types import BROADCAST_TX_COMMIT +from planetmint.transactions.types.assets.create import Create +from planetmint.transactions.types.elections.election import Election, Vote from planetmint.tendermint_utils import key_to_base64 @@ -29,11 +30,10 @@ def flush_localmongo_db(connection, dbname): def generate_block(planet): - from planetmint.common.crypto import generate_key_pair - from planetmint.models import Transaction + from planetmint.transactions.common.crypto import generate_key_pair alice = generate_key_pair() - tx = Transaction.create([alice.public_key], + tx = Create.generate([alice.public_key], [([alice.public_key], 1)], asset=None)\ .sign([alice.private_key]) diff --git a/tests/validation/test_transaction_structure.py b/tests/validation/test_transaction_structure.py index b710253..5fcb425 100644 --- a/tests/validation/test_transaction_structure.py +++ b/tests/validation/test_transaction_structure.py @@ -16,10 +16,10 @@ except ImportError: import sha3 from unittest.mock import MagicMock -from planetmint.common.exceptions import (AmountError, - SchemaValidationError, - ThresholdTooDeep) +from planetmint.transactions.common.exceptions import ( + AmountError, SchemaValidationError, ThresholdTooDeep) from planetmint.models import Transaction +from planetmint.transactions.common.utils import _fulfillment_to_details, _fulfillment_from_details ################################################################################ # Helper functions @@ -54,8 +54,8 @@ def test_tx_serialization_hash_function(signed_create_tx): def test_tx_serialization_with_incorrect_hash(signed_create_tx): - from planetmint.common.transaction import Transaction - from planetmint.common.exceptions import InvalidHash + from planetmint.transactions.common.transaction import Transaction + from planetmint.transactions.common.exceptions import InvalidHash tx = signed_create_tx.to_dict() tx['id'] = 'a' * 64 with pytest.raises(InvalidHash): @@ -63,7 +63,7 @@ def test_tx_serialization_with_incorrect_hash(signed_create_tx): def test_tx_serialization_with_no_hash(signed_create_tx): - from planetmint.common.exceptions import InvalidHash + from planetmint.transactions.common.exceptions import InvalidHash tx = signed_create_tx.to_dict() del tx['id'] with pytest.raises(InvalidHash): @@ -104,7 +104,7 @@ def test_validate_fails_metadata_empty_dict(b, create_tx, alice): # Asset def test_transfer_asset_schema(user_sk, signed_transfer_tx): - from planetmint.common.transaction import Transaction + from planetmint.transactions.common.transaction import Transaction tx = signed_transfer_tx.to_dict() validate(tx) tx['id'] = None @@ -149,7 +149,7 @@ def test_no_inputs(b, create_tx, alice): def test_create_single_input(b, create_tx, alice): - from planetmint.common.transaction import Transaction + from planetmint.transactions.common.transaction import Transaction tx = create_tx.to_dict() tx['inputs'] += tx['inputs'] tx = Transaction.from_dict(tx).sign([alice.private_key]).to_dict() @@ -161,7 +161,7 @@ def test_create_single_input(b, create_tx, alice): def test_create_tx_no_fulfills(b, create_tx, alice): - from planetmint.common.transaction import Transaction + from planetmint.transactions.common.transaction import Transaction tx = create_tx.to_dict() tx['inputs'][0]['fulfills'] = {'transaction_id': 'a' * 64, 'output_index': 0} @@ -213,8 +213,6 @@ def test_high_amounts(b, create_tx, alice): # Conditions def test_handle_threshold_overflow(): - from planetmint.common import transaction - cond = { 'type': 'ed25519-sha-256', 'public_key': 'a' * 43, @@ -226,18 +224,17 @@ def test_handle_threshold_overflow(): 'subconditions': [cond], } with pytest.raises(ThresholdTooDeep): - transaction._fulfillment_from_details(cond) + _fulfillment_from_details(cond) def test_unsupported_condition_type(): - from planetmint.common import transaction from cryptoconditions.exceptions import UnsupportedTypeError with pytest.raises(UnsupportedTypeError): - transaction._fulfillment_from_details({'type': 'a'}) + _fulfillment_from_details({'type': 'a'}) with pytest.raises(UnsupportedTypeError): - transaction._fulfillment_to_details(MagicMock(type_name='a')) + _fulfillment_to_details(MagicMock(type_name='a')) ################################################################################ diff --git a/tests/web/test_assets.py b/tests/web/test_assets.py index 9c632ef..b88c2ef 100644 --- a/tests/web/test_assets.py +++ b/tests/web/test_assets.py @@ -4,6 +4,7 @@ # Code is Apache-2.0 and docs are CC-BY-4.0 import pytest +from planetmint.transactions.types.assets.create import Create ASSETS_ENDPOINT = '/api/v1/assets/' @@ -22,7 +23,6 @@ def test_get_assets_with_missing_text_search(client): @pytest.mark.bdb def test_get_assets_tendermint(client, b, alice): - from planetmint.models import Transaction # test returns empty list when no assets are found res = client.get(ASSETS_ENDPOINT + '?search=abc') @@ -31,7 +31,7 @@ def test_get_assets_tendermint(client, b, alice): # create asset asset = {'msg': 'abc'} - tx = Transaction.create([alice.public_key], [([alice.public_key], 1)], + tx = Create.generate([alice.public_key], [([alice.public_key], 1)], asset=asset).sign([alice.private_key]) b.store_bulk_transactions([tx]) @@ -48,14 +48,13 @@ def test_get_assets_tendermint(client, b, alice): @pytest.mark.bdb def test_get_assets_limit_tendermint(client, b, alice): - from planetmint.models import Transaction # create two assets asset1 = {'msg': 'abc 1'} asset2 = {'msg': 'abc 2'} - tx1 = Transaction.create([alice.public_key], [([alice.public_key], 1)], + tx1 = Create.generate([alice.public_key], [([alice.public_key], 1)], asset=asset1).sign([alice.private_key]) - tx2 = Transaction.create([alice.public_key], [([alice.public_key], 1)], + tx2 = Create.generate([alice.public_key], [([alice.public_key], 1)], asset=asset2).sign([alice.private_key]) b.store_bulk_transactions([tx1]) diff --git a/tests/web/test_block_tendermint.py b/tests/web/test_block_tendermint.py index 2accbce..7fb034d 100644 --- a/tests/web/test_block_tendermint.py +++ b/tests/web/test_block_tendermint.py @@ -5,7 +5,7 @@ import pytest -from planetmint.models import Transaction +from planetmint.transactions.types.assets.create import Create from planetmint.lib import Block BLOCKS_ENDPOINT = '/api/v1/blocks/' @@ -15,7 +15,7 @@ BLOCKS_ENDPOINT = '/api/v1/blocks/' @pytest.mark.usefixtures('inputs') def test_get_block_endpoint(b, client, alice): import copy - tx = Transaction.create([alice.public_key], [([alice.public_key], 1)], asset={'cycle': 'hero'}) + tx = Create.generate([alice.public_key], [([alice.public_key], 1)], asset={'cycle': 'hero'}) tx = tx.sign([alice.private_key]) # with store_bulk_transactions we use `insert_many` where PyMongo @@ -48,7 +48,7 @@ def test_get_block_returns_404_if_not_found(client): @pytest.mark.bdb def test_get_block_containing_transaction(b, client, alice): - tx = Transaction.create([alice.public_key], [([alice.public_key], 1)], asset={'cycle': 'hero'}) + tx = Create.generate([alice.public_key], [([alice.public_key], 1)], asset={'cycle': 'hero'}) tx = tx.sign([alice.private_key]) b.store_bulk_transactions([tx]) diff --git a/tests/web/test_blocks.py b/tests/web/test_blocks.py index 54aa134..7dfc00e 100644 --- a/tests/web/test_blocks.py +++ b/tests/web/test_blocks.py @@ -17,6 +17,12 @@ def test_get_block_returns_404_if_not_found(client): res = client.get(BLOCKS_ENDPOINT + '123/') assert res.status_code == 404 + res = client.get(BLOCKS_ENDPOINT + 'latest') + assert res.status_code == 200 + + res = client.get(BLOCKS_ENDPOINT + 'latest/') + assert res.status_code == 200 + @pytest.mark.bdb def test_get_blocks_by_txid_endpoint_returns_empty_list_not_found(client): diff --git a/tests/web/test_metadata.py b/tests/web/test_metadata.py index d9301f3..9e2acf2 100644 --- a/tests/web/test_metadata.py +++ b/tests/web/test_metadata.py @@ -4,6 +4,7 @@ # Code is Apache-2.0 and docs are CC-BY-4.0 import pytest +from planetmint.transactions.types.assets.create import Create METADATA_ENDPOINT = '/api/v1/metadata/' @@ -22,7 +23,6 @@ def test_get_metadata_with_missing_text_search(client): @pytest.mark.bdb def test_get_metadata_tendermint(client, b, alice): - from planetmint.models import Transaction # test returns empty list when no assets are found res = client.get(METADATA_ENDPOINT + '?search=abc') @@ -32,7 +32,7 @@ def test_get_metadata_tendermint(client, b, alice): # create asset asset = {'msg': 'abc'} metadata = {'key': 'my_meta'} - tx = Transaction.create([alice.public_key], [([alice.public_key], 1)], metadata=metadata, + tx = Create.generate([alice.public_key], [([alice.public_key], 1)], metadata=metadata, asset=asset).sign([alice.private_key]) b.store_bulk_transactions([tx]) @@ -49,18 +49,17 @@ def test_get_metadata_tendermint(client, b, alice): @pytest.mark.bdb def test_get_metadata_limit_tendermint(client, b, alice): - from planetmint.models import Transaction # create two assets asset1 = {'msg': 'abc 1'} meta1 = {'key': 'meta 1'} - tx1 = Transaction.create([alice.public_key], [([alice.public_key], 1)], metadata=meta1, + tx1 = Create.generate([alice.public_key], [([alice.public_key], 1)], metadata=meta1, asset=asset1).sign([alice.private_key]) b.store_bulk_transactions([tx1]) asset2 = {'msg': 'abc 2'} meta2 = {'key': 'meta 2'} - tx2 = Transaction.create([alice.public_key], [([alice.public_key], 1)], metadata=meta2, + tx2 = Create.generate([alice.public_key], [([alice.public_key], 1)], metadata=meta2, asset=asset2).sign([alice.private_key]) b.store_bulk_transactions([tx2]) diff --git a/tests/web/test_outputs.py b/tests/web/test_outputs.py index db4e77a..16783b8 100644 --- a/tests/web/test_outputs.py +++ b/tests/web/test_outputs.py @@ -3,7 +3,10 @@ # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) # Code is Apache-2.0 and docs are CC-BY-4.0 + import pytest +from planetmint.transactions.types.assets.create import Create +from planetmint.transactions.types.assets.transfer import Transfer from unittest.mock import MagicMock, patch @@ -83,8 +86,7 @@ def test_get_outputs_endpoint_with_invalid_spent(client, user_pk): @pytest.mark.abci def test_get_divisble_transactions_returns_500(b, client): - from planetmint.models import Transaction - from planetmint.common import crypto + from planetmint.transactions.common import crypto import json TX_ENDPOINT = '/api/v1/transactions' @@ -96,7 +98,7 @@ def test_get_divisble_transactions_returns_500(b, client): bob_priv, bob_pub = crypto.generate_key_pair() carly_priv, carly_pub = crypto.generate_key_pair() - create_tx = Transaction.create([alice_pub], [([alice_pub], 4)]) + create_tx = Create.generate([alice_pub], [([alice_pub], 4)]) create_tx.sign([alice_priv]) res = client.post(TX_ENDPOINT, data=json.dumps(create_tx.to_dict())) @@ -104,7 +106,7 @@ def test_get_divisble_transactions_returns_500(b, client): mine([create_tx]) - transfer_tx = Transaction.transfer(create_tx.to_inputs(), + transfer_tx = Transfer.generate(create_tx.to_inputs(), [([alice_pub], 3), ([bob_pub], 1)], asset_id=create_tx.id) transfer_tx.sign([alice_priv]) @@ -114,7 +116,7 @@ def test_get_divisble_transactions_returns_500(b, client): mine([transfer_tx]) - transfer_tx_carly = Transaction.transfer([transfer_tx.to_inputs()[1]], + transfer_tx_carly = Transfer.generate([transfer_tx.to_inputs()[1]], [([carly_pub], 1)], asset_id=create_tx.id) transfer_tx_carly.sign([bob_priv]) diff --git a/tests/web/test_transactions.py b/tests/web/test_transactions.py index 3d2a73d..b613c50 100644 --- a/tests/web/test_transactions.py +++ b/tests/web/test_transactions.py @@ -14,10 +14,11 @@ try: except ImportError: from sha3 import sha3_256 -from planetmint.common import crypto -from planetmint.common.transaction_mode_types import (BROADCAST_TX_COMMIT, - BROADCAST_TX_ASYNC, - BROADCAST_TX_SYNC) +from planetmint.transactions.common import crypto +from planetmint.transactions.types.assets.create import Create +from planetmint.transactions.types.assets.transfer import Transfer +from planetmint.transactions.common.transaction_mode_types import ( + BROADCAST_TX_COMMIT, BROADCAST_TX_ASYNC, BROADCAST_TX_SYNC) TX_ENDPOINT = '/api/v1/transactions/' @@ -39,10 +40,9 @@ def test_get_transaction_returns_404_if_not_found(client): @pytest.mark.abci def test_post_create_transaction_endpoint(b, client): - from planetmint.models import Transaction user_priv, user_pub = crypto.generate_key_pair() - tx = Transaction.create([user_pub], [([user_pub], 1)]) + tx = Create.generate([user_pub], [([user_pub], 1)]) tx = tx.sign([user_priv]) res = client.post(TX_ENDPOINT, data=json.dumps(tx.to_dict())) @@ -68,7 +68,6 @@ def test_post_create_transaction_endpoint(b, client): @pytest.mark.language def test_post_create_transaction_with_language(b, client, nested, language, expected_status_code): - from planetmint.models import Transaction from planetmint.backend.localmongodb.connection import LocalMongoDBConnection if isinstance(b.connection, LocalMongoDBConnection): @@ -80,7 +79,7 @@ def test_post_create_transaction_with_language(b, client, nested, language, else: asset = lang_obj - tx = Transaction.create([user_pub], [([user_pub], 1)], + tx = Create.generate([user_pub], [([user_pub], 1)], asset=asset) tx = tx.sign([user_priv]) res = client.post(TX_ENDPOINT, data=json.dumps(tx.to_dict())) @@ -106,16 +105,15 @@ def test_post_create_transaction_with_language(b, client, nested, language, ]) def test_post_create_transaction_with_invalid_key(b, client, field, value, err_key, expected_status_code): - from planetmint.models import Transaction from planetmint.backend.localmongodb.connection import LocalMongoDBConnection user_priv, user_pub = crypto.generate_key_pair() if isinstance(b.connection, LocalMongoDBConnection): if field == 'asset': - tx = Transaction.create([user_pub], [([user_pub], 1)], + tx = Create.generate([user_pub], [([user_pub], 1)], asset=value) elif field == 'metadata': - tx = Transaction.create([user_pub], [([user_pub], 1)], + tx = Create.generate([user_pub], [([user_pub], 1)], metadata=value) tx = tx.sign([user_priv]) res = client.post(TX_ENDPOINT, data=json.dumps(tx.to_dict())) @@ -133,11 +131,10 @@ def test_post_create_transaction_with_invalid_key(b, client, field, value, @pytest.mark.abci @patch('planetmint.web.views.base.logger') def test_post_create_transaction_with_invalid_id(mock_logger, b, client): - from planetmint.common.exceptions import InvalidHash - from planetmint.models import Transaction + from planetmint.transactions.common.exceptions import InvalidHash user_priv, user_pub = crypto.generate_key_pair() - tx = Transaction.create([user_pub], [([user_pub], 1)]) + tx = Create.generate([user_pub], [([user_pub], 1)]) tx = tx.sign([user_priv]).to_dict() tx['id'] = 'abcd' * 16 @@ -170,11 +167,10 @@ def test_post_create_transaction_with_invalid_id(mock_logger, b, client): def test_post_create_transaction_with_invalid_signature(mock_logger, b, client): - from planetmint.common.exceptions import InvalidSignature - from planetmint.models import Transaction + from planetmint.transactions.common.exceptions import InvalidSignature user_priv, user_pub = crypto.generate_key_pair() - tx = Transaction.create([user_pub], [([user_pub], 1)]).to_dict() + tx = Create.generate([user_pub], [([user_pub], 1)]).to_dict() tx['inputs'][0]['fulfillment'] = 64 * '0' tx['id'] = sha3_256( json.dumps( @@ -218,9 +214,8 @@ def test_post_create_transaction_with_invalid_structure(client): @pytest.mark.abci @patch('planetmint.web.views.base.logger') def test_post_create_transaction_with_invalid_schema(mock_logger, client): - from planetmint.models import Transaction user_priv, user_pub = crypto.generate_key_pair() - tx = Transaction.create([user_pub], [([user_pub], 1)]).to_dict() + tx = Create.generate([user_pub], [([user_pub], 1)]).to_dict() del tx['version'] ed25519 = Ed25519Sha256(public_key=base58.b58decode(user_pub)) message = json.dumps( @@ -274,7 +269,7 @@ def test_post_create_transaction_with_invalid_schema(mock_logger, client): )) @patch('planetmint.web.views.base.logger') def test_post_invalid_transaction(mock_logger, client, exc, msg, monkeypatch,): - from planetmint.common import exceptions + from planetmint.transactions.common import exceptions exc_cls = getattr(exceptions, exc) def mock_validation(self_, tx): @@ -308,9 +303,8 @@ def test_post_invalid_transaction(mock_logger, client, exc, msg, monkeypatch,): @pytest.mark.abci def test_post_transfer_transaction_endpoint(client, user_pk, user_sk, posted_create_tx): - from planetmint.models import Transaction - transfer_tx = Transaction.transfer(posted_create_tx.to_inputs(), + transfer_tx = Transfer.generate(posted_create_tx.to_inputs(), [([user_pk], 1)], asset_id=posted_create_tx.id) transfer_tx = transfer_tx.sign([user_sk]) @@ -325,10 +319,9 @@ def test_post_transfer_transaction_endpoint(client, user_pk, user_sk, posted_cre @pytest.mark.abci def test_post_invalid_transfer_transaction_returns_400(client, user_pk, posted_create_tx): - from planetmint.models import Transaction - from planetmint.common.exceptions import InvalidSignature + from planetmint.transactions.common.exceptions import InvalidSignature - transfer_tx = Transaction.transfer(posted_create_tx.to_inputs(), + transfer_tx = Transfer.generate(posted_create_tx.to_inputs(), [([user_pk], 1)], asset_id=posted_create_tx.id) transfer_tx._hash() @@ -343,18 +336,17 @@ def test_post_invalid_transfer_transaction_returns_400(client, user_pk, posted_c @pytest.mark.abci def test_post_wrong_asset_division_transfer_returns_400(b, client, user_pk): - from planetmint.models import Transaction - from planetmint.common.exceptions import AmountError + from planetmint.transactions.common.exceptions import AmountError priv_key, pub_key = crypto.generate_key_pair() - create_tx = Transaction.create([pub_key], + create_tx = Create.generate([pub_key], [([pub_key], 10)], asset={'test': 'asset'}).sign([priv_key]) res = client.post(TX_ENDPOINT + '?mode=commit', data=json.dumps(create_tx.to_dict())) assert res.status_code == 202 - transfer_tx = Transaction.transfer(create_tx.to_inputs(), + transfer_tx = Transfer.generate(create_tx.to_inputs(), [([pub_key], 20)], # 20 > 10 asset_id=create_tx.id).sign([priv_key]) res = client.post(TX_ENDPOINT + '?mode=commit', data=json.dumps(transfer_tx.to_dict())) @@ -424,8 +416,7 @@ def test_transactions_get_list_bad(client): ('?mode=commit', BROADCAST_TX_COMMIT), ]) def test_post_transaction_valid_modes(mock_post, client, mode): - from planetmint.models import Transaction - from planetmint.common.crypto import generate_key_pair + from planetmint.transactions.common.crypto import generate_key_pair def _mock_post(*args, **kwargs): return Mock(json=Mock(return_value={'result': {'code': 0}})) @@ -433,7 +424,7 @@ def test_post_transaction_valid_modes(mock_post, client, mode): mock_post.side_effect = _mock_post alice = generate_key_pair() - tx = Transaction.create([alice.public_key], + tx = Create.generate([alice.public_key], [([alice.public_key], 1)], asset=None) \ .sign([alice.private_key]) @@ -445,10 +436,9 @@ def test_post_transaction_valid_modes(mock_post, client, mode): @pytest.mark.abci def test_post_transaction_invalid_mode(client): - from planetmint.models import Transaction - from planetmint.common.crypto import generate_key_pair + from planetmint.transactions.common.crypto import generate_key_pair alice = generate_key_pair() - tx = Transaction.create([alice.public_key], + tx = Create.generate([alice.public_key], [([alice.public_key], 1)], asset=None) \ .sign([alice.private_key]) diff --git a/tests/web/test_websocket_server.py b/tests/web/test_websocket_server.py index 54546d9..696e1ea 100644 --- a/tests/web/test_websocket_server.py +++ b/tests/web/test_websocket_server.py @@ -8,7 +8,9 @@ import json import queue import threading from unittest.mock import patch -from planetmint.config import Config +from planetmint.transactions.types.assets.create import Create +from planetmint.transactions.types.assets.transfer import Transfer + import pytest @@ -22,15 +24,14 @@ class MockWebSocket: def test_eventify_block_works_with_any_transaction(): from planetmint.web.websocket_server import eventify_block - from planetmint.common.crypto import generate_key_pair - from planetmint.lib import Transaction + from planetmint.transactions.common.crypto import generate_key_pair alice = generate_key_pair() - tx = Transaction.create([alice.public_key], + tx = Create.generate([alice.public_key], [([alice.public_key], 1)])\ .sign([alice.private_key]) - tx_transfer = Transaction.transfer(tx.to_inputs(), + tx_transfer = Transfer.generate(tx.to_inputs(), [([alice.public_key], 1)], asset_id=tx.id)\ .sign([alice.private_key]) @@ -139,11 +140,10 @@ async def test_websocket_string_event(test_client, loop): async def test_websocket_block_event( test_client, loop): from planetmint import events from planetmint.web.websocket_server import init_app, POISON_PILL, EVENTS_ENDPOINT - from planetmint.models import Transaction - from planetmint.common import crypto + from planetmint.transactions.common import crypto user_priv, user_pub = crypto.generate_key_pair() - tx = Transaction.create([user_pub], [([user_pub], 1)]) + tx = Create.generate([user_pub], [([user_pub], 1)]) tx = tx.sign([user_priv]) event_source = asyncio.Queue(loop=loop) @@ -181,11 +181,10 @@ def test_integration_from_webapi_to_websocket(monkeypatch, client, loop): import random import aiohttp - from planetmint.common import crypto + from planetmint.transactions.common import crypto # TODO processes does not exist anymore, when reactivating this test it # will fail because of this from planetmint import processes - from planetmint.models import Transaction # Start Planetmint processes.start() @@ -204,7 +203,7 @@ def test_integration_from_webapi_to_websocket(monkeypatch, client, loop): # Create a keypair and generate a new asset user_priv, user_pub = crypto.generate_key_pair() asset = {'random': random.random()} - tx = Transaction.create([user_pub], [([user_pub], 1)], asset=asset) + tx = Create.generate([user_pub], [([user_pub], 1)], asset=asset) tx = tx.sign([user_priv]) # Post the transaction to the Planetmint Web API client.post('/api/v1/transactions/', data=json.dumps(tx.to_dict())) diff --git a/tox.ini b/tox.ini index 4adc024..0cc9c26 100644 --- a/tox.ini +++ b/tox.ini @@ -35,5 +35,5 @@ deps = typing-extensions -r{toxinidir}/docs/root/requirements.txt extras = None -commands = sphinx-build -W -b html -d {envtmpdir}/doctrees . {envtmpdir}/html +commands = sphinx-build -b html -d {envtmpdir}/doctrees . {envtmpdir}/html