From 83ca51c94f2e9577f6728fffb9cb0dc020604186 Mon Sep 17 00:00:00 2001 From: Lorenz Herzberger <64837895+LaurentMontBlanc@users.noreply.github.com> Date: Fri, 1 Jul 2022 09:15:31 +0200 Subject: [PATCH] Planetmint tarantool (#169) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 31 restructue documentation (#138) * removed korean documentation Signed-off-by: Jürgen Eckel * removed CN and KOR readme Signed-off-by: Jürgen Eckel * changed to the press theme Signed-off-by: Jürgen Eckel * first changes Signed-off-by: Jürgen Eckel * fixe H3 vs H1 issues Signed-off-by: Jürgen Eckel * added missing png Signed-off-by: Jürgen Eckel * added missing file Signed-off-by: Jürgen Eckel * fixed warnings Signed-off-by: Jürgen Eckel * moved documents Signed-off-by: Jürgen Eckel * removed obsolete files Signed-off-by: Jürgen Eckel * removed obsolete folder Signed-off-by: Jürgen Eckel * removed obs. file Signed-off-by: Jürgen Eckel * added some final changes Signed-off-by: Jürgen Eckel * removed obs. reference Signed-off-by: Jürgen Eckel * moved chain migration to election types (#109) Signed-off-by: Lorenz Herzberger * Final zenroom (#147) * zenroom fixes Signed-off-by: Jürgen Eckel * expl. defined the aiohttp package Signed-off-by: Jürgen Eckel * increased version number and fixed a zenroom runtime bug Signed-off-by: Jürgen Eckel * added fialing zenroom tx signing test Signed-off-by: Jürgen Eckel * extended test to pass zenrooom validation, but to fail planetmint validation. Signed-off-by: Jürgen Eckel * added manual tx crafting Signed-off-by: Jürgen Eckel * added zenroom fulfillment verification Signed-off-by: Jürgen Eckel * the last mile before integration Signed-off-by: Jürgen Eckel * zenroom unit tests are passing Signed-off-by: Jürgen Eckel * simplified zenroom unit tests Signed-off-by: Jürgen Eckel * removed obsolte lines from the zenroom tests Signed-off-by: Jürgen Eckel * fixed acceptance tests Signed-off-by: Jürgen Eckel * adjusted zenroom integraiton tests Signed-off-by: Jürgen Eckel * fixed linting errors Signed-off-by: Jürgen Eckel * simplified zenroom unit test Signed-off-by: Jürgen Eckel * increased version number Signed-off-by: Jürgen Eckel * using cryptoconditions without print message Signed-off-by: Jürgen Eckel * increased cc usage to 0.9.9 readded daemon proceses Signed-off-by: Jürgen Eckel * increased version to 0.9.6 Signed-off-by: Jürgen Eckel * fixed deployment issue for 0.9.6 Signed-off-by: Jürgen Eckel * adjusted get_assets and from_db for tarantool Signed-off-by: Lorenz Herzberger * added comment Signed-off-by: Lorenz Herzberger * improve usability of zenroom (#159) * improve usability of zenroom * * increased version * fixed test cases * added changelog Signed-off-by: Jürgen Eckel Co-authored-by: Jürgen Eckel * migrated to AGPLv3 Signed-off-by: Jürgen Eckel * 150 add cryptoconditions documentation (#166) * added smaller logos fixed reference issue Signed-off-by: Jürgen Eckel * fixed some erros and typos Signed-off-by: Jürgen Eckel * added cryptoconditions reference to the subproject Signed-off-by: Jürgen Eckel * docker all in one now install tarantool Signed-off-by: Lorenz Herzberger * added user to integration init.lua Signed-off-by: Lorenz Herzberger * updated integration test setup for tarantool Signed-off-by: Lorenz Herzberger * removed print statements Signed-off-by: Lorenz Herzberger * updated changelog Signed-off-by: Lorenz Herzberger * fixed error messaging Signed-off-by: Jürgen Eckel * fixed exception verification Signed-off-by: Jürgen Eckel * fixed printing of testdata Signed-off-by: Jürgen Eckel Co-authored-by: Jürgen Eckel Co-authored-by: Lorenz Herzberger <64837895+LaurentDeMontBlanc@users.noreply.github.com> Co-authored-by: Alberto Lerda <30939098+albertolerda@users.noreply.github.com> Co-authored-by: Jürgen Eckel --- CHANGELOG.md | 22 + Dockerfile-all-in-one | 14 +- LICENSE | 798 ++++++++++++++---- README_cn.md | 77 -- README_kor.md | 65 -- acceptance/python/Dockerfile | 56 +- acceptance/python/src/conftest.py | 72 +- acceptance/python/src/test_zenroom.py | 133 +-- docs/root/.vscode/settings.json | 3 + .../generate_http_server_api_documentation.py | 2 +- docs/root/requirements.txt | 4 +- .../_static/Node-components.png | Bin .../source/_static/PLANETMINT_COLOR_POS.png | Bin 0 -> 23492 bytes .../_static/mongodb_cloud_manager_1.png | Bin 0 -> 12196 bytes docs/root/source/_static/planet-mint-logo.png | Bin 0 -> 3824 bytes docs/root/source/_static/planet-mint-logo.svg | 13 + docs/root/source/_static/planetmint-logo.png | Bin 0 -> 3824 bytes docs/root/source/_static/planetmint-logo.svg | 13 + .../root/source/_static/planetmint350x150.png | Bin 0 -> 7995 bytes .../source/_static/planetmint360x150white.png | Bin 0 -> 8189 bytes .../appendices/cryptography.rst | 0 .../appendices/firewall-notes.md | 2 +- .../appendices/generate-key-pair-for-ssh.md | 0 .../{installation => }/appendices/index.rst | 0 .../{installation => }/appendices/licenses.md | 0 .../appendices/log-rotation.md | 2 +- .../appendices/ntp-notes.md | 0 docs/root/source/basic-usage.md | 14 +- docs/root/source/conf.py | 150 ++-- .../_static/Conditions_Circuit_Diagram.png | Bin .../connecting/_static/Node-components.png | Bin 0 -> 17894 bytes .../_static/arch.jpg | Bin .../_static/cc_escrow_execute_abort.png | Bin .../_static/models_diagrams.odg | Bin .../_static/mongodb_cloud_manager_1.png | Bin 0 -> 12196 bytes .../_static/monitoring_system_diagram.png | Bin .../_static/stories_3_assets.png | Bin .../_static/tx_escrow_execute_abort.png | Bin ...x_multi_condition_multi_fulfillment_v1.png | Bin .../_static/tx_schematics.odg | Bin ...single_condition_single_fulfillment_v1.png | Bin .../commands-and-backend/backend.rst | 27 +- .../commands-and-backend/commands.rst | 0 .../commands-and-backend/index.rst | 11 +- .../the-planetmint-class.rst | 0 .../index.rst => connecting/drivers.rst} | 9 +- .../http-client-server-api.rst | 38 +- .../http-samples/api-index-response.http | 2 +- .../http-samples/get-block-request.http | 0 .../http-samples/get-block-response.http | 0 .../http-samples/get-block-txid-request.http | 0 .../http-samples/get-block-txid-response.http | 0 .../http-samples/get-tx-by-asset-request.http | 0 .../get-tx-by-asset-response.http | 0 .../http-samples/get-tx-id-request.http | 0 .../http-samples/get-tx-id-response.http | 0 .../http-samples/index-response.http | 6 +- .../http-samples/post-tx-request.http | 0 .../http-samples/post-tx-response.http | 0 docs/root/source/connecting/index.rst | 23 + docs/root/source/{ => connecting}/query.rst | 11 +- .../websocket-event-stream-api.rst | 22 +- .../root/source/cryptoconditions.md | 0 docs/root/source/index.rst | 33 +- docs/root/source/installation/api/index.rst | 16 - docs/root/source/installation/index.rst | 20 - .../source/installation/node-setup/index.rst | 25 - .../node-setup/production-node/index.rst | 17 - .../installation/node-setup/release-notes.md | 16 - docs/root/source/installation/quickstart.md | 91 -- .../{ => introduction}/about-planetmint.rst | 33 +- docs/root/source/introduction/index.rst | 10 + .../source/{ => introduction}/properties.md | 2 +- docs/root/source/introduction/quickstart.md | 80 ++ .../_static/CREATE_and_TRANSFER_example.png | Bin 4149 -> 0 bytes .../source/korean/_static/CREATE_example.png | Bin 1806 -> 0 bytes docs/root/source/korean/_static/schemaDB.png | Bin 169926 -> 0 bytes docs/root/source/korean/assets_ko.md | 21 - docs/root/source/korean/bft-ko.md | 13 - docs/root/source/korean/decentralized_kor.md | 24 - docs/root/source/korean/diversity-ko.md | 18 - docs/root/source/korean/immutable-ko.md | 27 - docs/root/source/korean/index.rst | 98 --- docs/root/source/korean/permissions-ko.md | 59 -- docs/root/source/korean/private-data-ko.md | 102 --- .../source/korean/production-ready_kor.md | 12 - docs/root/source/korean/query-ko.md | 202 ----- docs/root/source/korean/smart-contracts_ko.md | 17 - docs/root/source/korean/store-files_ko.md | 14 - docs/root/source/korean/terminology_kor.md | 26 - .../source/korean/transaction-concepts_ko.md | 61 -- .../network-setup/index.rst | 17 +- .../k8s-deployment-template/architecture.rst | 0 .../ca-installation.rst | 0 .../client-tls-certificate.rst | 0 .../k8s-deployment-template/cloud-manager.rst | 0 .../k8s-deployment-template/easy-rsa.rst | 0 .../k8s-deployment-template/index.rst | 0 .../k8s-deployment-template/log-analytics.rst | 0 .../node-config-map-and-secrets.rst | 0 .../node-on-kubernetes.rst | 0 .../planetmint-network-on-kubernetes.rst | 0 .../revoke-tls-certificate.rst | 0 .../server-tls-certificate.rst | 0 .../tectonic-azure.rst | 0 .../template-kubernetes-azure.rst | 0 .../k8s-deployment-template/troubleshoot.rst | 0 .../upgrade-on-kubernetes.rst | 0 .../k8s-deployment-template/workflow.rst | 0 .../network-setup/network-setup.md | 2 +- .../network-setup/networks.md | 2 +- .../node-setup/all-in-one-planetmint.md | 2 +- .../node-setup/aws-setup.md | 0 .../node-setup/configuration.md | 4 +- .../node-setup/deploy-a-machine.md | 2 +- docs/root/source/node-setup/index.rst | 31 + .../node-setup/planetmint-node-ansible.md | 0 .../node-setup/production-node/index.rst | 20 + .../production-node/node-assumptions.md | 0 .../production-node/node-components.md | 0 .../production-node/node-requirements.md | 0 .../node-security-and-privacy.md | 0 .../production-node/reverse-proxy-notes.md | 0 .../node-setup/set-up-nginx.md | 0 .../node-setup/set-up-node-software.md | 0 docs/root/source/terminology.md | 6 +- docs/root/source/tools/index.rst | 8 + .../node-setup => tools}/planetmint-cli.md | 10 +- .../node-setup => }/troubleshooting.md | 2 +- integration/python/Dockerfile | 6 +- integration/python/src/conftest.py | 66 +- integration/python/src/test_zenroom.py | 155 ++-- integration/scripts/all-in-one.bash | 7 +- integration/scripts/init.lua | 86 ++ planetmint/__init__.py | 4 +- planetmint/backend/connection.py | 3 - planetmint/backend/tarantool/connection.py | 42 +- planetmint/commands/planetmint.py | 2 +- planetmint/start.py | 18 +- planetmint/transactions/common/output.py | 156 ++-- .../common/schema/v2.0/transaction.yaml | 6 +- planetmint/transactions/common/transaction.py | 659 +++++++++------ planetmint/transactions/common/utils.py | 165 ++-- .../elections}/chain_migration_election.py | 0 planetmint/version.py | 6 +- planetmint/web/views/transactions.py | 39 +- setup.py | 183 ++-- tests/assets/test_divisible_assets.py | 1 - tests/assets/test_zenroom_signing.py | 172 ++++ tests/commands/test_commands.py | 2 +- tests/elections/test_election.py | 2 +- tests/migrations/test_migration_election.py | 2 +- tests/tendermint/test_core.py | 2 +- tests/test_core.py | 2 +- tests/web/test_transactions.py | 442 +++++----- tox.ini | 2 +- 156 files changed, 2591 insertions(+), 2371 deletions(-) delete mode 100644 README_cn.md delete mode 100644 README_kor.md create mode 100644 docs/root/.vscode/settings.json rename docs/root/source/{installation => }/_static/Node-components.png (100%) create mode 100644 docs/root/source/_static/PLANETMINT_COLOR_POS.png create mode 100644 docs/root/source/_static/mongodb_cloud_manager_1.png create mode 100644 docs/root/source/_static/planet-mint-logo.png create mode 100644 docs/root/source/_static/planet-mint-logo.svg create mode 100644 docs/root/source/_static/planetmint-logo.png create mode 100644 docs/root/source/_static/planetmint-logo.svg create mode 100644 docs/root/source/_static/planetmint350x150.png create mode 100644 docs/root/source/_static/planetmint360x150white.png rename docs/root/source/{installation => }/appendices/cryptography.rst (100%) rename docs/root/source/{installation => }/appendices/firewall-notes.md (96%) rename docs/root/source/{installation => }/appendices/generate-key-pair-for-ssh.md (100%) rename docs/root/source/{installation => }/appendices/index.rst (100%) rename docs/root/source/{installation => }/appendices/licenses.md (100%) rename docs/root/source/{installation => }/appendices/log-rotation.md (94%) rename docs/root/source/{installation => }/appendices/ntp-notes.md (100%) rename docs/root/source/{installation => connecting}/_static/Conditions_Circuit_Diagram.png (100%) create mode 100644 docs/root/source/connecting/_static/Node-components.png rename docs/root/source/{installation => connecting}/_static/arch.jpg (100%) rename docs/root/source/{installation => connecting}/_static/cc_escrow_execute_abort.png (100%) rename docs/root/source/{installation => connecting}/_static/models_diagrams.odg (100%) create mode 100644 docs/root/source/connecting/_static/mongodb_cloud_manager_1.png rename docs/root/source/{installation => connecting}/_static/monitoring_system_diagram.png (100%) rename docs/root/source/{installation => connecting}/_static/stories_3_assets.png (100%) rename docs/root/source/{installation => connecting}/_static/tx_escrow_execute_abort.png (100%) rename docs/root/source/{installation => connecting}/_static/tx_multi_condition_multi_fulfillment_v1.png (100%) rename docs/root/source/{installation => connecting}/_static/tx_schematics.odg (100%) rename docs/root/source/{installation => connecting}/_static/tx_single_condition_single_fulfillment_v1.png (100%) rename docs/root/source/{installation => connecting}/commands-and-backend/backend.rst (78%) rename docs/root/source/{installation => connecting}/commands-and-backend/commands.rst (100%) rename docs/root/source/{installation => connecting}/commands-and-backend/index.rst (82%) rename docs/root/source/{installation => connecting}/commands-and-backend/the-planetmint-class.rst (100%) rename docs/root/source/{drivers/index.rst => connecting/drivers.rst} (92%) rename docs/root/source/{installation/api => connecting}/http-client-server-api.rst (98%) rename docs/root/source/{installation/api => connecting}/http-samples/api-index-response.http (85%) rename docs/root/source/{installation/api => connecting}/http-samples/get-block-request.http (100%) rename docs/root/source/{installation/api => connecting}/http-samples/get-block-response.http (100%) rename docs/root/source/{installation/api => connecting}/http-samples/get-block-txid-request.http (100%) rename docs/root/source/{installation/api => connecting}/http-samples/get-block-txid-response.http (100%) rename docs/root/source/{installation/api => connecting}/http-samples/get-tx-by-asset-request.http (100%) rename docs/root/source/{installation/api => connecting}/http-samples/get-tx-by-asset-response.http (100%) rename docs/root/source/{installation/api => connecting}/http-samples/get-tx-id-request.http (100%) rename docs/root/source/{installation/api => connecting}/http-samples/get-tx-id-response.http (100%) rename docs/root/source/{installation/api => connecting}/http-samples/index-response.http (82%) rename docs/root/source/{installation/api => connecting}/http-samples/post-tx-request.http (100%) rename docs/root/source/{installation/api => connecting}/http-samples/post-tx-response.http (100%) create mode 100644 docs/root/source/connecting/index.rst rename docs/root/source/{ => connecting}/query.rst (98%) rename docs/root/source/{installation/api => connecting}/websocket-event-stream-api.rst (93%) rename planetmint/migrations/__init__.py => docs/root/source/cryptoconditions.md (100%) delete mode 100644 docs/root/source/installation/api/index.rst delete mode 100644 docs/root/source/installation/index.rst delete mode 100644 docs/root/source/installation/node-setup/index.rst delete mode 100644 docs/root/source/installation/node-setup/production-node/index.rst delete mode 100644 docs/root/source/installation/node-setup/release-notes.md delete mode 100644 docs/root/source/installation/quickstart.md rename docs/root/source/{ => introduction}/about-planetmint.rst (91%) create mode 100644 docs/root/source/introduction/index.rst rename docs/root/source/{ => introduction}/properties.md (99%) create mode 100644 docs/root/source/introduction/quickstart.md delete mode 100644 docs/root/source/korean/_static/CREATE_and_TRANSFER_example.png delete mode 100644 docs/root/source/korean/_static/CREATE_example.png delete mode 100644 docs/root/source/korean/_static/schemaDB.png delete mode 100644 docs/root/source/korean/assets_ko.md delete mode 100644 docs/root/source/korean/bft-ko.md delete mode 100644 docs/root/source/korean/decentralized_kor.md delete mode 100644 docs/root/source/korean/diversity-ko.md delete mode 100644 docs/root/source/korean/immutable-ko.md delete mode 100644 docs/root/source/korean/index.rst delete mode 100644 docs/root/source/korean/permissions-ko.md delete mode 100644 docs/root/source/korean/private-data-ko.md delete mode 100644 docs/root/source/korean/production-ready_kor.md delete mode 100644 docs/root/source/korean/query-ko.md delete mode 100644 docs/root/source/korean/smart-contracts_ko.md delete mode 100644 docs/root/source/korean/store-files_ko.md delete mode 100644 docs/root/source/korean/terminology_kor.md delete mode 100644 docs/root/source/korean/transaction-concepts_ko.md rename docs/root/source/{installation => }/network-setup/index.rst (68%) rename docs/root/source/{installation => }/network-setup/k8s-deployment-template/architecture.rst (100%) rename docs/root/source/{installation => }/network-setup/k8s-deployment-template/ca-installation.rst (100%) rename docs/root/source/{installation => }/network-setup/k8s-deployment-template/client-tls-certificate.rst (100%) rename docs/root/source/{installation => }/network-setup/k8s-deployment-template/cloud-manager.rst (100%) rename docs/root/source/{installation => }/network-setup/k8s-deployment-template/easy-rsa.rst (100%) rename docs/root/source/{installation => }/network-setup/k8s-deployment-template/index.rst (100%) rename docs/root/source/{installation => }/network-setup/k8s-deployment-template/log-analytics.rst (100%) rename docs/root/source/{installation => }/network-setup/k8s-deployment-template/node-config-map-and-secrets.rst (100%) rename docs/root/source/{installation => }/network-setup/k8s-deployment-template/node-on-kubernetes.rst (100%) rename docs/root/source/{installation => }/network-setup/k8s-deployment-template/planetmint-network-on-kubernetes.rst (100%) rename docs/root/source/{installation => }/network-setup/k8s-deployment-template/revoke-tls-certificate.rst (100%) rename docs/root/source/{installation => }/network-setup/k8s-deployment-template/server-tls-certificate.rst (100%) rename docs/root/source/{installation => }/network-setup/k8s-deployment-template/tectonic-azure.rst (100%) rename docs/root/source/{installation => }/network-setup/k8s-deployment-template/template-kubernetes-azure.rst (100%) rename docs/root/source/{installation => }/network-setup/k8s-deployment-template/troubleshoot.rst (100%) rename docs/root/source/{installation => }/network-setup/k8s-deployment-template/upgrade-on-kubernetes.rst (100%) rename docs/root/source/{installation => }/network-setup/k8s-deployment-template/workflow.rst (100%) rename docs/root/source/{installation => }/network-setup/network-setup.md (97%) rename docs/root/source/{installation => }/network-setup/networks.md (94%) rename docs/root/source/{installation => }/node-setup/all-in-one-planetmint.md (97%) rename docs/root/source/{installation => }/node-setup/aws-setup.md (100%) rename docs/root/source/{installation => }/node-setup/configuration.md (98%) rename docs/root/source/{installation => }/node-setup/deploy-a-machine.md (96%) create mode 100644 docs/root/source/node-setup/index.rst rename docs/root/source/{installation => }/node-setup/planetmint-node-ansible.md (100%) create mode 100644 docs/root/source/node-setup/production-node/index.rst rename docs/root/source/{installation => }/node-setup/production-node/node-assumptions.md (100%) rename docs/root/source/{installation => }/node-setup/production-node/node-components.md (100%) rename docs/root/source/{installation => }/node-setup/production-node/node-requirements.md (100%) rename docs/root/source/{installation => }/node-setup/production-node/node-security-and-privacy.md (100%) rename docs/root/source/{installation => }/node-setup/production-node/reverse-proxy-notes.md (100%) rename docs/root/source/{installation => }/node-setup/set-up-nginx.md (100%) rename docs/root/source/{installation => }/node-setup/set-up-node-software.md (100%) create mode 100644 docs/root/source/tools/index.rst rename docs/root/source/{installation/node-setup => tools}/planetmint-cli.md (96%) rename docs/root/source/{installation/node-setup => }/troubleshooting.md (96%) create mode 100644 integration/scripts/init.lua rename planetmint/{migrations => transactions/types/elections}/chain_migration_election.py (100%) create mode 100644 tests/assets/test_zenroom_signing.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 47709a5..30d7b61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,28 @@ For reference, the possible headings are: * **Known Issues** * **Notes** +## [Unreleased] +### Feature Update +Tarantool integration + +## [0.9.8] - 2022-06-27 + +### Feature Update +Changed license to AGPLv3 + + +## [0.9.7] - 2022-06-17 + +### Feature Update +Deep Zenroom integration + +## [0.9.6] - 2022-06-08 + +### Maintenance + +* removed Korean documentation +* removed Korean and Chinese README + ## [2.2.2] - 2020-08-12 ### Security diff --git a/Dockerfile-all-in-one b/Dockerfile-all-in-one index c2d1c1e..5c807a7 100644 --- a/Dockerfile-all-in-one +++ b/Dockerfile-all-in-one @@ -16,12 +16,11 @@ RUN apt-get update \ && pip install -e . \ && apt-get autoremove -# Install mongodb and monit +# Install tarantool and 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 +RUN curl -L https://tarantool.io/wrATeGF/release/2/installer.sh | bash +RUN apt-get install -y tarantool monit # Install Tendermint RUN wget https://github.com/tendermint/tendermint/releases/download/v${TM_VERSION}/tendermint_${TM_VERSION}_linux_amd64.tar.gz \ @@ -31,13 +30,10 @@ RUN wget https://github.com/tendermint/tendermint/releases/download/v${TM_VERSIO ENV TMHOME=/tendermint -# Set permissions required for mongodb -RUN mkdir -p /data/db /data/configdb \ - && chown -R mongodb:mongodb /data/db /data/configdb - # Planetmint enviroment variables -ENV PLANETMINT_DATABASE_PORT 27017 +ENV PLANETMINT_DATABASE_PORT 3303 ENV PLANETMINT_DATABASE_BACKEND tarantool_db +ENV PLANETMINT_DATABASE_HOST localhost ENV PLANETMINT_SERVER_BIND 0.0.0.0:9984 ENV PLANETMINT_WSSERVER_HOST 0.0.0.0 ENV PLANETMINT_WSSERVER_SCHEME ws diff --git a/LICENSE b/LICENSE index 261eeb9..0ad25db 100644 --- a/LICENSE +++ b/LICENSE @@ -1,201 +1,661 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. - 1. Definitions. + Preamble - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. + The precise terms and conditions for copying, distribution and +modification follow. - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." + TERMS AND CONDITIONS - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. + 0. Definitions. - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. + "This License" refers to version 3 of the GNU Affero General Public License. - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and + A "covered work" means either the unmodified Program or a work based +on the Program. - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. + 1. Source Code. - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. - END OF TERMS AND CONDITIONS + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. - APPENDIX: How to apply the Apache License to your work. + The Corresponding Source for a work in source code form is that +same work. - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. + 2. Basic Permissions. - Copyright [yyyy] [name of copyright owner] + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. - http://www.apache.org/licenses/LICENSE-2.0 + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/README_cn.md b/README_cn.md deleted file mode 100644 index 8c1cb8c..0000000 --- a/README_cn.md +++ /dev/null @@ -1,77 +0,0 @@ - - - - -[![Codecov branch](https://img.shields.io/codecov/c/github/planetmint/planetmint/master.svg)](https://codecov.io/github/planetmint/planetmint?branch=master) -[![Latest release](https://img.shields.io/github/release/planetmint/planetmint/all.svg)](https://github.com/planetmint/planetmint/releases) -[![Status on PyPI](https://img.shields.io/pypi/status/planetmint.svg)](https://pypi.org/project/Planetmint/) -[![Travis branch](https://img.shields.io/travis/planetmint/planetmint/master.svg)](https://travis-ci.com/planetmint/planetmint) -[![Documentation Status](https://readthedocs.org/projects/planetmint-server/badge/?version=latest)](https://docs.planetmint.com/projects/server/en/latest/) -[![Join the chat at https://gitter.im/planetmint/planetmint](https://badges.gitter.im/planetmint/planetmint.svg)](https://gitter.im/planetmint/planetmint?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) - -# Planetmint 服务器 - -Planetmint 是区块链数据库. 这是 _BigchainDB 服务器_ 的仓库. - -## 基础知识 - -* [尝试快速开始](https://docs.planetmint.com/projects/server/en/latest/quickstart.html) -* [阅读 Planetmint 2.0 白皮书](https://www.planetmint.com/whitepaper/) -* [查阅漫游指南](https://www.planetmint.com/developers/guide/) - -## 运行和测试 `master` 分支的 Planetmint 服务器 - -运行和测试最新版本的 Planetmint 服务器非常简单. 确认你有安装最新版本的 [Docker Compose](https://docs.docker.com/compose/install/). 当你准备好了, 打开一个终端并运行: - -```text -git clone https://github.com/planetmint/planetmint.git -cd planetmint -make run -``` - -Planetmint 应该可以通过 `http://localhost:9984/` 访问. - -这里也有一些其他的命令你可以运行: - -* `make start`: 通过源码和守护进程的方式运行 Planetmint (通过 `make stop` 停止). -* `make stop`: 停止运行 Planetmint. -* `make logs`: 附在日志上. -* `make test`: 运行所有单元和验收测试. -* `make test-unit-watch`: 运行所有测试并等待. 每次更改代码时都会再次运行测试. -* `make cov`: 检查代码覆盖率并在浏览器中打开结果. -* `make doc`: 生成 HTML 文档并在浏览器中打开它. -* `make clean`: 删除所有构建, 测试, 覆盖和 Python 生成物. -* `make reset`: 停止并移除所有容器. 警告: 您将丢失存储在 Planetmint 中的所有数据. - -查看所有可用命令, 请运行 `make`. - -## 一般人员链接 - -* [Planetmint.com](https://www.planetmint.com/) - Planetmint 主网站, 包括新闻订阅 -* [路线图](https://github.com/planetmint/org/blob/master/ROADMAP.md) -* [博客](https://medium.com/the-planetmint-blog) -* [推特](https://twitter.com/Planetmint) - -## 开发人员链接 - -* [所有的 Planetmint 文档](https://docs.planetmint.com/en/latest/) -* [Planetmint 服务器 文档](https://docs.planetmint.com/projects/server/en/latest/index.html) -* [CONTRIBUTING.md](.github/CONTRIBUTING.md) - how to contribute -* [社区指南](CODE_OF_CONDUCT.md) -* [公开问题](https://github.com/planetmint/planetmint/issues) -* [公开的 pull request](https://github.com/planetmint/planetmint/pulls) -* [Gitter 聊天室](https://gitter.im/planetmint/planetmint) - -## 法律声明 - -* [许可](LICENSES.md) - 开源代码 & 开源内容 -* [印记](https://www.planetmint.com/imprint/) -* [联系我们](https://www.planetmint.com/contact/) diff --git a/README_kor.md b/README_kor.md deleted file mode 100644 index 2982e51..0000000 --- a/README_kor.md +++ /dev/null @@ -1,65 +0,0 @@ -[![Codecov branch](https://img.shields.io/codecov/c/github/planetmint/planetmint/master.svg)](https://codecov.io/github/planetmint/planetmint?branch=master) -[![Latest release](https://img.shields.io/github/release/planetmint/planetmint/all.svg)](https://github.com/planetmint/planetmint/releases) -[![Status on PyPI](https://img.shields.io/pypi/status/planetmint.svg)](https://pypi.org/project/Planetmint/) -[![Travis branch](https://img.shields.io/travis/planetmint/planetmint/master.svg)](https://travis-ci.org/planetmint/planetmint) -[![Documentation Status](https://readthedocs.org/projects/planetmint-server/badge/?version=latest)](https://docs.planetmint.com/projects/server/en/latest/) -[![Join the chat at https://gitter.im/planetmint/planetmint](https://badges.gitter.im/planetmint/planetmint.svg)](https://gitter.im/planetmint/planetmint?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) - -# Planetmint 서버 - -BigchaingDB는 블록체인 데이터베이스입니다. 이 저장소는 _BigchaingDB 서버_를 위한 저장소입니다. - -### 기본 사항 - -* [빠른 시작 사용해보기](https://docs.planetmint.com/projects/server/en/latest/quickstart.html) -* [Planetmint 2.0 백서 읽기](https://www.planetmint.com/whitepaper/) -* [BigchainDB에 대한 _Hitchiker's Guide_를 확인십시오.](https://www.planetmint.com/developers/guide/) - -### `master` Branch에서 Planetmint 서버 실행 및 테스트 - -BigchaingDB 서버의 최신 버전을 실행하고 테스트하는 것은 어렵지 않습니다. [Docker Compose](https://docs.docker.com/compose/install/)의 최신 버전이 설치되어 있는지 확인하십시오. 준비가 되었다면, 터미널에서 다음을 실행하십시오. - -```text -git clone https://github.com/planetmint/planetmint.git -cd planetmint -make run -``` - -이제 BigchainDB는 `http://localhost:9984/`에 연결되어야 합니다. - -또한, 실행시키기 위한 다른 명령어들도 있습니다. - -* `make start` : 소스로부터 BigchainDB를 실행하고 데몬화합니다. \(이는 `make stop` 을 하면 중지합니다.\) -* `make stop` : BigchainDB를 중지합니다. -* `make logs` : 로그에 첨부합니다. -* `make text` : 모든 유닛과 허가 테스트를 실행합니다. -* `make test-unit-watch` : 모든 테스트를 수행하고 기다립니다. 코드를 변경할 때마다 테스트는 다시 실행될 것입니다. -* `make cov` : 코드 커버리지를 확인하고 브라우저에서 결과를 엽니다. -* `make doc` : HTML 문서를 만들고, 브라우저에서 엽니다. -* `make clean` : 모든 빌드와 테스트, 커버리지 및 파이썬 아티팩트를 제거합니다. -* `make reset` : 모든 컨테이너들을 중지하고 제거합니다. 경고 : BigchainDB에 저장된 모든 데이터를 잃을 수 있습니다. - -사용 가능한 모든 명령어를 보기 위해서는 `make` 를 실행하십시오. - -### 모두를 위한 링크들 - -* [Planetmint.com ](https://www.planetmint.com/)- 뉴스 레터 가입을 포함하는 Planetmint 주요 웹 사이트 -* [로드맵](https://github.com/planetmint/org/blob/master/ROADMAP.md) -* [블로그](https://medium.com/the-planetmint-blog) -* [트위터](https://twitter.com/Planetmint) - -### 개발자들을 위한 링크들 - -* [모든 Planetmint 문서](https://docs.planetmint.com/en/latest/) -* [Planetmint 서버 문서](https://docs.planetmint.com/projects/server/en/latest/index.html) -* [CONTRIBUTING.md](https://github.com/planetmint/planetmint/blob/master/.github/CONTRIBUTING.md) - 기여를 하는 방법 -* [커뮤니티 가이드라인](https://github.com/planetmint/planetmint/blob/master/CODE_OF_CONDUCT.md) -* [이슈 작성](https://github.com/planetmint/planetmint/issues) -* [pull request 하기](https://github.com/planetmint/planetmint/pulls) -* [Gitter 채팅방](https://gitter.im/planetmint/planetmint) - -### 합법 - -* [라이선스](https://github.com/planetmint/planetmint/blob/master/LICENSES.md) - 오픈 소스 & 오픈 콘텐츠 -* [발행](https://www.planetmint.com/imprint/) -* [연락처](https://www.planetmint.com/contact/) diff --git a/acceptance/python/Dockerfile b/acceptance/python/Dockerfile index 269446b..966b30e 100644 --- a/acceptance/python/Dockerfile +++ b/acceptance/python/Dockerfile @@ -1,64 +1,18 @@ FROM python:3.9 RUN apt-get update \ - && pip install -U pip \ - && apt-get autoremove \ - && apt-get clean + && 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 \ + planetmint-cryptoconditions>=0.9.9\ + planetmint-driver>=0.9.2 \ 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 index 34e8a3f..3a4912e 100644 --- a/acceptance/python/src/conftest.py +++ b/acceptance/python/src/conftest.py @@ -5,37 +5,36 @@ 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""" +CONDITION_SCRIPT = """ + Scenario 'ecdh': create the signature of an object + Given I have the 'keyring' + Given that I have a 'string dictionary' named 'houses' inside 'asset' + When I create the signature of 'houses' + Then print the 'signature'""" -# 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 +FULFILL_SCRIPT = """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' + Given I have a 'signature' named 'signature' inside 'metadata' + When I verify the 'houses' has a signature in 'signature' by 'Alice' Then print the string 'ok'""" +SK_TO_PK = """Scenario 'ecdh': Create the keypair + Given that I am known as '{}' + Given I have the 'keyring' + When I create the ecdh public key + When I create the bitcoin address + Then print my 'ecdh public key' + Then print my 'bitcoin address'""" + +GENERATE_KEYPAIR = """Scenario 'ecdh': Create the keypair + Given that I am known as 'Pippo' + When I create the ecdh key + When I create the bitcoin key + Then print data""" + +ZENROOM_DATA = {"also": "more data"} + HOUSE_ASSETS = { "data": { "houses": [ @@ -46,44 +45,39 @@ HOUSE_ASSETS = { { "name": "Draco", "team": "Slytherin", - } + }, ], } } -ZENROOM_DATA = { - 'also': 'more data' -} +metadata = {"units": 300, "type": "KG"} -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 + return ZENROOM_DATA diff --git a/acceptance/python/src/test_zenroom.py b/acceptance/python/src/test_zenroom.py index 2829fab..c5b33bf 100644 --- a/acceptance/python/src/test_zenroom.py +++ b/acceptance/python/src/test_zenroom.py @@ -1,68 +1,73 @@ -# 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 os import json +import base58 +from hashlib import sha3_256 +from cryptoconditions.types.ed25519 import Ed25519Sha256 +from cryptoconditions.types.zenroom import ZenroomSha256 +from zenroom import zencode_exec +from planetmint_driver import Planetmint +from planetmint_driver.crypto import generate_keypair -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) +def test_zenroom_signing(gen_key_zencode, secret_key_to_private_key_zencode, + fulfill_script_zencode, zenroom_data, zenroom_house_assets, + condition_script_zencode): + + biolabs = generate_keypair() + version = '2.0' + + alice = json.loads(zencode_exec(gen_key_zencode).output)['keyring'] + bob = json.loads(zencode_exec(gen_key_zencode).output)['keyring'] + + zen_public_keys = json.loads(zencode_exec(secret_key_to_private_key_zencode.format('Alice'), + keys=json.dumps({'keyring': alice})).output) + zen_public_keys.update(json.loads(zencode_exec(secret_key_to_private_key_zencode.format('Bob'), + keys=json.dumps({'keyring': bob})).output)) + + + + zenroomscpt = ZenroomSha256(script=fulfill_script_zencode, data=zenroom_data, keys=zen_public_keys) + print(F'zenroom is: {zenroomscpt.script}') + # CRYPTO-CONDITIONS: generate the condition uri - condition_uri = zenSha.condition.serialize_uri() + condition_uri_zen = zenroomscpt.condition.serialize_uri() + print(F'\nzenroom condition URI: {condition_uri_zen}') # CRYPTO-CONDITIONS: construct an unsigned fulfillment dictionary - unsigned_fulfillment_dict = { - 'type': zenSha.TYPE_NAME, - 'script': fulfill_script_zencode, - 'keys': zen_public_keys, + unsigned_fulfillment_dict_zen = { + 'type': zenroomscpt.TYPE_NAME, + 'public_key': base58.b58encode(biolabs.public_key).decode(), } - output = { - 'amount': '1000', + 'amount': '10', 'condition': { - 'details': unsigned_fulfillment_dict, - 'uri': condition_uri, + 'details': unsigned_fulfillment_dict_zen, + 'uri': condition_uri_zen, + }, - 'data': zenroom_data, - 'script': fulfill_script_zencode, - 'conf': '', - 'public_keys': (zen_public_keys['Alice']['ecdh_public_key'], ), + 'public_keys': [biolabs.public_key,], } - - input_ = { 'fulfillment': None, 'fulfills': None, - 'owners_before': (zen_public_keys['Alice']['ecdh_public_key'], ), + 'owners_before': [biolabs.public_key,] } - + metadata = { + "result": { + "output": ["ok"] + } + } + token_creation_tx = { 'operation': 'CREATE', 'asset': zenroom_house_assets, - 'metadata': None, - 'outputs': (output,), - 'inputs': (input_,), - 'version': '2.0', + 'metadata': metadata, + 'outputs': [output,], + 'inputs': [input_,], + 'version': version, 'id': None, } @@ -74,12 +79,36 @@ condition_script_zencode, zenroom_data, zenroom_house_assets): ensure_ascii=False, ) - try: - assert(not zenSha.validate(message=message)) - except JSONDecodeError: - pass - except ValueError: - pass + # major workflow: + # we store the fulfill script in the transaction/message (zenroom-sha) + # the condition script is used to fulfill the transaction and create the signature + # + # the server should ick the fulfill script and recreate the zenroom-sha and verify the signature - message = zenSha.sign(message, condition_script_zencode, alice) - assert(zenSha.validate(message=message)) + + + message = zenroomscpt.sign(message, condition_script_zencode, alice) + assert(zenroomscpt.validate(message=message)) + + message = json.loads(message) + fulfillment_uri_zen = zenroomscpt.serialize_uri() + + message['inputs'][0]['fulfillment'] = fulfillment_uri_zen + tx = message + tx['id'] = None + json_str_tx = json.dumps( + tx, + sort_keys=True, + skipkeys=False, + separators=(',', ':') + ) + # SHA3: hash the serialized id-less transaction to generate the id + shared_creation_txid = sha3_256(json_str_tx.encode()).hexdigest() + message['id'] = shared_creation_txid + + + # `https://example.com:9984` + plntmnt = Planetmint(os.environ.get('PLANETMINT_ENDPOINT')) + sent_transfer_tx = plntmnt.transactions.send_commit(message) + + print( f"\n\nstatus and result : + {sent_transfer_tx}") diff --git a/docs/root/.vscode/settings.json b/docs/root/.vscode/settings.json new file mode 100644 index 0000000..65e1ec0 --- /dev/null +++ b/docs/root/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "makefile.extensionOutputFolder": "./.vscode" +} \ No newline at end of file diff --git a/docs/root/generate_http_server_api_documentation.py b/docs/root/generate_http_server_api_documentation.py index 1bf66ae..a51cae5 100644 --- a/docs/root/generate_http_server_api_documentation.py +++ b/docs/root/generate_http_server_api_documentation.py @@ -190,7 +190,7 @@ def main(): base_path = os.path.join(os.path.dirname(__file__), - 'source/installation/api/http-samples') + 'source/connecting/http-samples') if not os.path.exists(base_path): os.makedirs(base_path) diff --git a/docs/root/requirements.txt b/docs/root/requirements.txt index 6d8ca1f..d80d8f1 100644 --- a/docs/root/requirements.txt +++ b/docs/root/requirements.txt @@ -36,4 +36,6 @@ sphinxcontrib-serializinghtml==1.1.5 urllib3==1.26.9 wget==3.2 zipp==3.8.0 -nest-asyncio==1.5.5 \ No newline at end of file +nest-asyncio==1.5.5 +sphinx-press-theme==0.8.0 +sphinx-documatt-theme diff --git a/docs/root/source/installation/_static/Node-components.png b/docs/root/source/_static/Node-components.png similarity index 100% rename from docs/root/source/installation/_static/Node-components.png rename to docs/root/source/_static/Node-components.png diff --git a/docs/root/source/_static/PLANETMINT_COLOR_POS.png b/docs/root/source/_static/PLANETMINT_COLOR_POS.png new file mode 100644 index 0000000000000000000000000000000000000000..1ce824bde9520b3836d1532fc385a7bdc9239dd9 GIT binary patch literal 23492 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 + + + + + + + + + + + + diff --git a/docs/root/source/_static/planetmint-logo.png b/docs/root/source/_static/planetmint-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..c923c0382700f512a2eeaaa97a9dd3e3a8a34862 GIT binary patch literal 3824 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 + + + + + + + + + + + + diff --git a/docs/root/source/_static/planetmint350x150.png b/docs/root/source/_static/planetmint350x150.png new file mode 100644 index 0000000000000000000000000000000000000000..3d10d7a117e765fdb10fd01e1ddafaf5c9c265ae GIT binary patch literal 7995 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 WqKx?n6=(l>YpLn0zEytY|9=4T(aYTc literal 0 HcmV?d00001 diff --git a/docs/root/source/_static/planetmint360x150white.png b/docs/root/source/_static/planetmint360x150white.png new file mode 100644 index 0000000000000000000000000000000000000000..70c1f5902faaea26ed0617d972451a13d5a08827 GIT binary patch literal 8189 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 CfZ4AA literal 0 HcmV?d00001 diff --git a/docs/root/source/installation/appendices/cryptography.rst b/docs/root/source/appendices/cryptography.rst similarity index 100% rename from docs/root/source/installation/appendices/cryptography.rst rename to docs/root/source/appendices/cryptography.rst diff --git a/docs/root/source/installation/appendices/firewall-notes.md b/docs/root/source/appendices/firewall-notes.md similarity index 96% rename from docs/root/source/installation/appendices/firewall-notes.md rename to docs/root/source/appendices/firewall-notes.md index 4b8ec39..2b3a397 100644 --- a/docs/root/source/installation/appendices/firewall-notes.md +++ b/docs/root/source/appendices/firewall-notes.md @@ -49,7 +49,7 @@ Port 443 is the default HTTPS port (TCP). Package managers might also get some p Port 9984 is the default port for the Planetmint client-server HTTP API (TCP), which is served by Gunicorn HTTP Server. It's _possible_ allow port 9984 to accept inbound traffic from anyone, but we recommend against doing that. Instead, set up a reverse proxy server (e.g. using Nginx) and only allow traffic from there. Information about how to do that can be found [in the Gunicorn documentation](http://docs.gunicorn.org/en/stable/deploy.html). (They call it a proxy.) -If Gunicorn and the reverse proxy are running on the same server, then you'll have to tell Gunicorn to listen on some port other than 9984 (so that the reverse proxy can listen on port 9984). You can do that by setting `server.bind` to 'localhost:PORT' in the [Planetmint Configuration Settings](../../installation/node-setup/configuration), where PORT is whatever port you chose (e.g. 9983). +If Gunicorn and the reverse proxy are running on the same server, then you'll have to tell Gunicorn to listen on some port other than 9984 (so that the reverse proxy can listen on port 9984). You can do that by setting `server.bind` to 'localhost:PORT' in the [Planetmint Configuration Settings](../node-setup/configuration), where PORT is whatever port you chose (e.g. 9983). You may want to have Gunicorn and the reverse proxy running on different servers, so that both can listen on port 9984. That would also help isolate the effects of a denial-of-service attack. diff --git a/docs/root/source/installation/appendices/generate-key-pair-for-ssh.md b/docs/root/source/appendices/generate-key-pair-for-ssh.md similarity index 100% rename from docs/root/source/installation/appendices/generate-key-pair-for-ssh.md rename to docs/root/source/appendices/generate-key-pair-for-ssh.md diff --git a/docs/root/source/installation/appendices/index.rst b/docs/root/source/appendices/index.rst similarity index 100% rename from docs/root/source/installation/appendices/index.rst rename to docs/root/source/appendices/index.rst diff --git a/docs/root/source/installation/appendices/licenses.md b/docs/root/source/appendices/licenses.md similarity index 100% rename from docs/root/source/installation/appendices/licenses.md rename to docs/root/source/appendices/licenses.md diff --git a/docs/root/source/installation/appendices/log-rotation.md b/docs/root/source/appendices/log-rotation.md similarity index 94% rename from docs/root/source/installation/appendices/log-rotation.md rename to docs/root/source/appendices/log-rotation.md index bfd6f68..e89aa27 100644 --- a/docs/root/source/installation/appendices/log-rotation.md +++ b/docs/root/source/appendices/log-rotation.md @@ -27,7 +27,7 @@ Planetmint Server writes its logs to two files: normal logs and error logs. The Log rotation is baked into Planetmint Server using Python's `logging` module. The logs for Planetmint Server are rotated when any of the above mentioned files exceeds 209715200 bytes (i.e. approximately 209 MB). -For more information, see the docs about [the Planetmint Server configuration settings related to logging](../../installation/node-setup/configuration#log). +For more information, see the docs about [the Planetmint Server configuration settings related to logging](../node-setup/configuration#log). ## Tendermint Logging and Log Rotation diff --git a/docs/root/source/installation/appendices/ntp-notes.md b/docs/root/source/appendices/ntp-notes.md similarity index 100% rename from docs/root/source/installation/appendices/ntp-notes.md rename to docs/root/source/appendices/ntp-notes.md diff --git a/docs/root/source/basic-usage.md b/docs/root/source/basic-usage.md index 7f8f393..ccb54f9 100644 --- a/docs/root/source/basic-usage.md +++ b/docs/root/source/basic-usage.md @@ -119,13 +119,19 @@ of the outgoing paperclips (100). ### Transaction Validity When a node is asked to check if a transaction is valid, it checks several -things. We documented those things in a post on *The Planetmint Blog*: -["What is a Valid Transaction in Planetmint?"](https://blog.planetmint.io/what-is-a-valid-transaction-in-planetmint-9a1a075a9598) +things. This got documentet by a BigchainDB post (previous version of Planetmint) at*The BigchainDB Blog*: +["What is a Valid Transaction in BigchainDB?"](https://blog.bigchaindb.com/what-is-a-valid-transaction-in-planetmint-9a1a075a9598) (Note: That post was about Planetmint Server v1.0.0.) ### Example Transactions There are example Planetmint transactions in -[the HTTP API documentation](./installation/api/http-client-server-api) +[the HTTP API documentation](./connecting/http-client-server-api) and -[the Python Driver documentation](./drivers/index). +[the Python Driver documentation](./connecting/drivers). + +## Contracts & Conditions + +Planetmint has been developed with simple logical gateways in mind. The logic got introduced by [cryptoconditions](https://https://docs.planetmint.io/projects/cryptoconditions). The cryptocondition documentation contains all details about how conditoins are defined and how they can be verified and fulfilled. + +The integration of such into the transaction schema of Planetmint is shown below. \ No newline at end of file diff --git a/docs/root/source/conf.py b/docs/root/source/conf.py index 5c082ea..8dc1e0e 100644 --- a/docs/root/source/conf.py +++ b/docs/root/source/conf.py @@ -30,14 +30,14 @@ from os import rename, remove # get version _version = {} -with open('../../../planetmint/version.py') as fp: +with open("../../../planetmint/version.py") as fp: exec(fp.read(), _version) currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) parentdir = os.path.dirname(currentdir) -sys.path.insert(0,parentdir) -#sys.path.insert(0, "/home/myname/pythonfiles") +sys.path.insert(0, parentdir) +# sys.path.insert(0, "/home/myname/pythonfiles") # -- General configuration ------------------------------------------------ @@ -48,83 +48,95 @@ 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' +project = "Planetmint" import sphinx_rtd_theme extensions = [ - 'myst_parser', - 'sphinx.ext.autosectionlabel', - 'sphinx.ext.autodoc', - 'sphinx.ext.intersphinx', - 'sphinx.ext.coverage', - 'sphinx.ext.viewcode', - 'sphinx.ext.todo', - 'sphinx.ext.napoleon', - 'sphinxcontrib.httpdomain', - 'aafigure.sphinxext', + "myst_parser", + "sphinx.ext.autosectionlabel", + "sphinx.ext.autodoc", + "sphinx.ext.intersphinx", + "sphinx.ext.coverage", + "sphinx.ext.viewcode", + "sphinx.ext.todo", + "sphinx.ext.napoleon", + "sphinxcontrib.httpdomain", + "aafigure.sphinxext", + #'sphinx_toolbox.collapse', # Below are actually build steps made to look like sphinx extensions. # It was the easiest way to get it running with ReadTheDocs. - 'generate_http_server_api_documentation', + "generate_http_server_api_documentation", ] try: - remove('contributing/cross-project-policies/code-of-conduct.md') - remove('contributing/cross-project-policies/release-process.md') - remove('contributing/cross-project-policies/python-style-guide.md') + remove("contributing/cross-project-policies/code-of-conduct.md") + remove("contributing/cross-project-policies/release-process.md") + remove("contributing/cross-project-policies/python-style-guide.md") except: - print('done') + print("done") + def get_old_new(url, old, new): filename = wget.download(url) rename(old, new) -get_old_new('https://raw.githubusercontent.com/planetmint/planetmint/master/CODE_OF_CONDUCT.md', - 'CODE_OF_CONDUCT.md', 'contributing/cross-project-policies/code-of-conduct.md') -get_old_new('https://raw.githubusercontent.com/planetmint/planetmint/master/RELEASE_PROCESS.md', - 'RELEASE_PROCESS.md', 'contributing/cross-project-policies/release-process.md') +get_old_new( + "https://raw.githubusercontent.com/planetmint/planetmint/master/CODE_OF_CONDUCT.md", + "CODE_OF_CONDUCT.md", + "contributing/cross-project-policies/code-of-conduct.md", +) -get_old_new('https://raw.githubusercontent.com/planetmint/planetmint/master/PYTHON_STYLE_GUIDE.md', - 'PYTHON_STYLE_GUIDE.md', 'contributing/cross-project-policies/python-style-guide.md') +get_old_new( + "https://raw.githubusercontent.com/planetmint/planetmint/master/RELEASE_PROCESS.md", + "RELEASE_PROCESS.md", + "contributing/cross-project-policies/release-process.md", +) -suppress_warnings = ['misc.highlighting_failure'] +get_old_new( + "https://raw.githubusercontent.com/planetmint/planetmint/master/PYTHON_STYLE_GUIDE.md", + "PYTHON_STYLE_GUIDE.md", + "contributing/cross-project-policies/python-style-guide.md", +) + +suppress_warnings = ["misc.highlighting_failure"] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # autodoc settings -autodoc_member_order = 'bysource' +autodoc_member_order = "bysource" autodoc_default_options = { - 'members': None, + "members": None, } # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] -source_suffix = ['.rst', '.md'] +source_suffix = [".rst", ".md"] # The encoding of source files. # # source_encoding = 'utf-8-sig' # The master toctree document. -master_doc = 'index' +master_doc = "index" autosectionlabel_prefix_document = True # General information about the project. now = datetime.datetime.now() -copyright = str(now.year) + ', Planetmint Contributors' -author = 'Planetmint Contributors' +copyright = str(now.year) + ", Planetmint Contributors" +author = "Planetmint Contributors" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -version = _version['__short_version__'] +version = _version["__short_version__"] # The full version, including alpha/beta/rc tags. -release = _version['__version__'] +release = _version["__version__"] # The full version, including alpha/beta/rc tags. # The language for content autogenerated by Sphinx. Refer to documentation @@ -132,7 +144,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 = 'en' +language = "en" # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: @@ -168,7 +180,7 @@ exclude_patterns = [] # show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] @@ -185,7 +197,8 @@ todo_include_todos = False # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_theme = 'sphinx_rtd_theme' +html_theme = "press" +# html_theme = 'sphinx_documatt_theme' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the @@ -194,7 +207,7 @@ html_theme = 'sphinx_rtd_theme' # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. -html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] +# html_theme_path = [press.get_html_theme_path()] # The name for this set of Sphinx documents. # " v documentation" by default. @@ -208,7 +221,7 @@ html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] # The name of an image file (relative to this directory) to place at the top # of the sidebar. # -# html_logo = None +html_logo = "_static/planetmint-logo.png" # The name of an image file (relative to this directory) to use as a favicon of # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 @@ -219,7 +232,7 @@ html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied @@ -299,34 +312,36 @@ html_static_path = ['_static'] # html_search_scorer = 'scorer.js' # Output file base name for HTML help builder. -htmlhelp_basename = 'Planetmintdoc' +htmlhelp_basename = "Planetmintdoc" # -- Options for LaTeX output --------------------------------------------- latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # - 'papersize': 'letterpaper', - - # The font size ('10pt', '11pt' or '12pt'). - # - # 'pointsize': '10pt', - - # Additional stuff for the LaTeX preamble. - # - # 'preamble': '', - - # Latex figure (float) alignment - # - # 'figure_align': 'htbp', + # The paper size ('letterpaper' or 'a4paper'). + # + "papersize": "letterpaper", + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'Planetmint.tex', 'Planetmint Documentation', - 'Planetmint Contributors', 'manual'), + ( + master_doc, + "Planetmint.tex", + "Planetmint Documentation", + "Planetmint Contributors", + "manual", + ), ] # The name of an image file (relative to this directory) to place at the top of @@ -366,10 +381,7 @@ latex_documents = [ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'planetmint', 'Planetmint Documentation', - [author], 1) -] +man_pages = [(master_doc, "planetmint", "Planetmint Documentation", [author], 1)] # If true, show URL addresses after external links. # @@ -382,9 +394,15 @@ man_pages = [ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'Planetmint', 'Planetmint Documentation', - author, 'Planetmint', 'One line description of project.', - 'Miscellaneous'), + ( + master_doc, + "Planetmint", + "Planetmint Documentation", + author, + "Planetmint", + "One line description of project.", + "Miscellaneous", + ), ] # Documents to append as an appendix to all manuals. diff --git a/docs/root/source/installation/_static/Conditions_Circuit_Diagram.png b/docs/root/source/connecting/_static/Conditions_Circuit_Diagram.png similarity index 100% rename from docs/root/source/installation/_static/Conditions_Circuit_Diagram.png rename to docs/root/source/connecting/_static/Conditions_Circuit_Diagram.png diff --git a/docs/root/source/connecting/_static/Node-components.png b/docs/root/source/connecting/_static/Node-components.png new file mode 100644 index 0000000000000000000000000000000000000000..4bc8e9a56092c694ca32e66738f01c0f9d6376ae GIT binary patch literal 17894 zcmb{ZWmuG5*8mLT~-$7_FlEuUTf_zH5EBLTyk6-92`9P*U}m|IRAifaBjuj zzYC1KXB$bt!I3GKmwu(?HM^ZbkepzdaY}9W?$+IBXSBEf!4D082Mfmy2eXlvH|FGI zt1E}ed}m-_At7fWBO@nIdFoKBZdO^>sG+{zjIhC+mzXIU)h@~EHVWHAW7KnUrVlfB zm#zi*PeoRT2hw?5ul>ZPk6R%_g2U~unHSzWyNAtzr~Yz;z*7F-{3P_%J-^aFbQEY_ zzsyoebvd|+67t#VrAW0$bvU7^ax+;wZUuez(xS(P*bWDW>CT2# zCXZGBj~B<48G?2mehbPC-JrJph2O&BXEK+|8SN1bHy+|Fw?(fHNyk>VSP5$BI`K<((;LQ}1-H6KT5qRL1k>+@+ z(6Kqw6%LM?+g&_zw}oV9IH&l@)}dCHcHXH;LrtA-rSWhM)@kJyM z^6s{?>pY%?eP3`nZUZ|czrA_)=4jc|Z^xZwabrUKX0K`5*7p38a}*DNK1IkaVtM$mi2?`b^N*vF;wG!!I_fJ{5&#A>*zqdX-CZ>K5sriN?t|uB;1@?K zpCe|Ld6uNEpG6?{eltePHDx8749Z=vu$Zaz)7Un!7fS21H zsW_ppyxQI1zjE!dneK8v;I$)@M3VpWa#6EgY4-wHBGa!gy6g5E%^q$>{=<3b6@cO0 zha9ami+k|hdV|%Wi}uvUp_#4+``)C6>6t842!LJT@>@c05PKwL) z$Co3kBtw+nj8KU{ol#@d$CU@)DZE3*JWaKeI<#>x=rnS1p_Ehzx8^oA;2jarB?Cn z8xaF_eR?W~UtI3Za5~>bl_edYc8{Nhtt=PMb8}*(wD(v1B}~O#`r# zY;(2=r$a1FriX|Zh|l51W8=uh_Lba*eAe$T$#{7|Nv-D2KA_#!l`c2pR}5bwVJ5w$ z-1SWF=x?pJm+17Cl1UTB zOD5xCta>bZzAzO^w?g8HUfh1WlG8i@<0IA2qe()IRBs*{s#9TErxlE= zU(vKeqr(hHW|UCed3+aH0=bhC=QjvTRpj2*UCC`8=xR-?&lA@jS8g*hIrgXJS9=Bl zyjRIAv{vLajsH=Qc(^q%TlIvS@LGUmwju`~;F4Kq?#EG{ax%#UlS4jGB5RIFsMGvI zJB@1TiMPpk#{r|~+-B#Q0V;|WuUd&0MpuK$V=1vVZ)pbBvN=EW`n{~mG{4Kp1A|DB zv?S{0mooZk7bO0>u6tlBRu!%@dM^d%`0c3OvDW%)p72z~__XY09XBbGW}ob}Iz0Lc zNP=Vv-c3Vw`eS*9e(Lc~bX#J7ZDv5BQcyhBZ+*@gfOQ4lU%mRIbyj+y!x`uFuNqmf zOhgjZN#;N(i+g$DH#tBnV^2ebrt68}pI)flDJu#+U@!YQ=}~(_NBjBe+&q9OHfNDi zCuK8`3|EHt(tfSdc(eJd@=NM=X$~n*}%U33-k>hWczeiraXg!{#o+#DpF3KLke)$f? z#DmDrWZrnf>YA-C#ivjxdop3dL){khn!H|w<;O3}lsvOQM=L1F~U?LH@l09}&5*gE*N(8(H;$V1@`p z@s_e}!Tzdk^@Ak~Zs@spoOoyHO@OMj_FtlFdC=68s3VcyZfMJCDrkJy=C01M!>Dhn zx^`!Qs)*SEh^IFdHR86%O(DK`)_fFMohbEIbJQ*i2vsr?uQ`(l)v()BJBD;Vkr1v? z;Xu6TG$r~HylK1P$!^!G0Qebv(m=tKNTwdz7gku&JOWIJPpjA!Am%6Y562UbsT?kF=a@>A+6OD*UwR*F{2uXu0;ul& zmGl7Y?v_S=o6Df6ev+_RU$0FuE0hCaHu{hcZjGYTrwU1dS9w18^R46n?1@x@N%lM* z<_#UY`56(p7Hd`yEQij=zc*np>|(qp*W}`a;Ex(inu0GgY02fJq+%|z{4q;`^3nF$ z9A)HjwCF{q@DDxTto;Lhu}5|57V;j|RuxJuAj~J%^v~(94uMFWJ%jvX!7>pcU1|Aj zOZ1p#{q?jiUSIxB88=1W@TctRowDnZRZ1?fF)BR!#RkWA zae}haIRl%GxrSX6mTlqV_3_inRy$k#i4*F*wAtBPH_1Xtt`>C6QvicRI~@W5ZUY_) z_y_P$*nhEt!NIz%|BU|8tp6GXB2EDIU!(7^Uf}QOUldpa2b>gk^uG}Q8U1g>|NrFv z&k+A5_rHjlwBK-Bn=2d=C8D>q9+A(t=@L7oxh{YU-QO;dkOidKGnWrm4L0cqj2|-( zuKj3RwbF-V7!-UWKD>Mj7)nXJK>pRv-E~%v*-uWi>{ElaquZ|Lw;E7e^{9z3cYdV% z>A&rR#=EEb8D@bOAKXv8Hrdv{wAEEE=|{db{1_hF_XOaQniHa$ODcZJ=CtXCSFEep za6mR=&cU8^K-Rp&>Rj0aIZR5Q4A*|JspPV<^iH_K?}T0SBMQqAtsGRSA$`ek(yD&( zu!4<`0-tv>t_*nr<|U|pK96m##Qmand(v(1Le*sx>^1_OLEbZ*$YCHryqJkueESz18@})mU!NKM$cWZ=NQ+nqsQ09a{Rn7ekp?10 z-j^uzi$6krGf}?8E%QFhnYOx%>zB(S_T8@ovUU?04UeHb~sH<=hUK{v!^ax@OGY~H>-T3)A*3p?T@)J3y%UcL-@usCB z75{#*aqwJxCzOuQwN^I7lYX!p%4_u!#QxP0Ii!7Zt`wHvl=@r}5i^ngBo)v9+6}g? z|Epk#GU8gbFBr9cH5Avvr2M+|0!s8hq=7KkJduZDjwcxoRc%myMW3b08PdhpO_T~J zC)J+WV0mp9!R2hmoNFtUTeeFJ=iU!rsF3|KgbMo!3 zno&&36eI!Z1b`oqNJzok&of{sZun5Q%BiB~XdE-xwNk{MWLho^vr#v2RrUk|+Ps0D zacB=6WzTX$kH!6ZpKfYdaE*JMGB>EsI~9I0e){xBv;N84>ttDah!J*$#`}&Uc8MlJNQcPd>E8)S-!Qt zqNfJ)KZ|Hxd))@k*?T)ZH?leYf?#zK|0ZL6l@5NC9lif zOEuG}4yK;@BL5F-9#$G1m-w5rEgux`1BUP^8dde}_4s(E{->Da#P3i&Petw5wzD(D zb$FB8x(6lJled3;j`+yi%=GPjQ8~Y28zgs#GV7#ye%oB%?YG+I;X`Y1=21Y}wt7Hk z!Y+Qm`1Xz$`ztpuQedm$k_avl+kTV%K=V!fx#Y*UW>+E?Z5!sVe_ReZ?QXk{mO!kM z@sa+M&JVDWwM&f6Se5zUr8^&X8y^0{+e8oM?NS)ul+zM}kQ)M-JXQuSdoQ__^cPy~<6nbrtv&Q@=dx#2fv zLs^)4kX7hO)3m(Q0O#QVipq0nias4}vokZuw(Y)ZFOWn4n80V@)tQxdgm?l_#e%#WrWxJT%J$xH`3^DzCnBH48|8L!e z>QAd%e-5AjI%cp33KK2w?*wF=EyX>0!}_D`xz)4T*?^mc_P}UALr5fxPhjK^(Sczx$O~#E5YF<@8hY&#a7=|-u2&ICV^Y$rPrGgo4VJ<7-5r8 zPtgYYMfABB#myPnN42n0=XvXZdmj}HFHUN|H3we5ZaGYG?27hV=BX@eTffbzB<_B2 zSR8Ue>U%8o(`qz(w0M*CyM;A0{Clg<@p%?cf$CXw;P1=sn|H+$_Yvi&`N2z8UzjWPa(*?{)FknU^lf44D%xdUzO9Zr)qd ziD5)t*GTv2Q^Bx5pIMHz)U4SDUJMY*QSA2@eY;m)wmDfkjW}Ex@-eXMn?9!&wGxv4Rl5gm8-UDv$ zh1RvtbONc^s*8XE>`u_e-TIw{t7nY&Uku!pERu8)kc}qar_LW!%&+&{HEzEeCAc;d zXT0|Z>T*w$x?S4-u?)tqadiRGLcg|0wJiCEVtqolc z$#q1FwWE&bpQ_;e5i5!HDi1pH-lL)`W&Ts?_2aSk=$nuKGjaCnQ10jT)kU30DQa!J zaE0^o_a9=D+5Wz7e!gvWP1M~z-5z8^GE+pdt9LEY03w;UtBY?ku zn*M&}FEk2>Ii8U1=PiTA`31GaQU%PQe*3}7`wO0I(WhIN4&O~eCpC;i zs08!gwx%Pn(q|nhR!DIZSe-mxuYkx^V zAePQjIjm;fUo{XQsts{$Kiv5p$)g%AN5v-+@WYi~*TOp^k)@JI19L1j%@NTak8Bs4 zKj6jGQS@(mUh`c`^CwP+bAc`lsG~_al0p1Yj*+|P9SpcycjWnNF#LfuR6=gnEl2(j z$5)Z_jA zX^^MoEjk}jBhju0@^L@!=(h0$tV~}piI8k{-Rz7td}pNK7Za^kZCY&9HYOQtyxiXX z+@e$E=0M7M<>YtiO!U(w(@z8AKHJOq?I9_9PK%D0ck4#b^!IL(;+Di#)TiZ4zn{+g z2Wbw4U$nv;S};++*G6gkxs#+Y?tNiI%bSkIgH2^m7HU^^ypGmjHSCp|jMO0X@BKM} zB<&m^of;+wr_$a#cyj_-&hZW;YTwS#RexQgR8poYZ0Ulc|2f!R#oYEYV|R%{CuQQ1 z*9Nm@UquUx{KfXJ?Fukdp(xst#cgl2uI4irS!M_iZ zcJnVDFyl%mZ045kMkX1$>-VBtDpeAuqZiNRhx8LRX-jv*(Y})yOLb$%Hh7*|-=@*N z@1m=vdm5fQ5bMOOZR%^^j@+VKm6o4WPHlRN0d-U2Wd2W~`2ipXM^})@eYEJf0%H zoAbAEQiv*BW!lwNJlg5?={(r`N#-L0fU7sz~sw>0TObsU4nSy2d@pty&tAYig zrU2Rj+5I8i`pnxxoM_*TBpG&b7J}f}Ma|}ul}i;$=A@>m!4p!2U2`ztM-Zdz;%*zC z#>oX)d@Z?l$5gRFeC_RhXu56bzRkFy?%{Ge7?i@%z^{!)vEI(Vn)hmfTHfCt$zj=5_nUlaveL7KeHl!Fp%O%WHzb zvW&3mD|(xm(3t(4?}`s+@reIy4qnE8!s)+xk1Q+FEtk^@k=^&2|LwT1n3s`=?@q8y zXZEmTBPQxw8q(q0wT~+&@=rBCV@JmkgqmE*CBk z`ELbuB@c{Gv#ABR<{yENQP_+;glYiV6Mwd8xW7ZcZh4Gqa$O9^GJQbSnf2|0lh5=- zo};OJzu(3pXS;J8r!FEJWItv_AbPHKzOI74`D!oj;Hjx<3my|XFH0sdtV@F{ttygO zSmpkc{}znQfnqe2gHD--oh$)QgVyPJubKTG zXsL*;K5G`s?Q7>E!}OP!U;HjF+?1x23JWro1VJZnx}@i(xz-x(h1-gIEx~?=P8XsC zzbwISc$*ibEWJ=oghT>Ue#7SC zO=iu65Aqk<^iOpDNrrc%;w9X^@k~NZHt94S(5eB_Kcn>6@zcz9zfv$>Ro(4Q1w?Sv(MC2T{g3l-Qxprzx#yE}&>XxWs{PGFlOJIwpWAnOzo9s-F>#zIE)ek$ z9+@d|RVel_<|5%^?-SG(L+P^C9U>32$yKDy#RJE)WcVIKCi^CuLAgd8u z&{lb41hA!Ew4bMU3iLhFsNk0qwz6po#&rM_e;={4Gq1M}2{!i!Os)2M8c*1!87Vl2 zH|yZxW7}UJ`0xg1zj8fZ;!xuwS<5(>=f5eh4auX0&_WBU(~vA+%656xa&F~BJ%h4p z+>!<88pU~gb6%hRLez0ns%Go}EXQVPHNYXnq_e?N|=GelMvZ8MGR~Sio&{W zs2&|4+3q%UD1R_G0ix#x*>=S-SBv2FH@ys+FoJZZnv@B9R&0K!i4juy%OhmILD8&x zIkSvvK){ncldw4=3yqnXRMCTCoXaaa19`LUmo;Was@uKn@eZjFH-OO|h~qi5>%v6& zJ;j0^d`PYUq?0qm!y%RiD01C5vyj?6Bdl_QrbH#<8F5Yp8)Eb<7kw;k0vBw+Y&Si~ zniXzPeU_xVUH6|eh;|jekg74^_Zd`(rX1o2J-s7OZYRmF-;O6_f-n;V$-i(Cx9U8Q zAo(9R^C5pvY6^ky$RtrgSt3 zuJw=D!Nr8kFopMIHEw(#XDM!YT{ z)_KH^EN-u3dSAZ+7WGvXT(?{BO)!uW6TuW=(p`iu7G6;-mPN$4wGr1<9d53wS!}>* z#%))%3Iw;{Z`B{CyuFz>KphZCyQbjv(`tC*cE7J4_3W%DGU5W6HBAH%t5-X;1h|dV zSMHKr7n~2cDBw=g=FbkuR{U#dg33|+UNYnRw*5OfohoKgNf?qlFbLzz|J^DN7&IA? zUG$X6F-tYq{Y!=@pc1%H%bsUvZn9SCZd1n*eyxWa?z5(6-|9p+VaSi8ZMbA=*5s30 zlv2>VBA3nIx1@ntOKTZ{_I!S$`2nt+u%ug zMy3IZkY%V-gV4s+`X#TWs9F|nucW3wM#MBpo`$?ng0>{?T}kT3W30hinQ=MOXO9j| zB1eO`KnoUNt^1vEg@rV&)F-&3E@s)2cnUFiWOxfulHmKzqp0u66a92HHzjNnk-5VY z_G&M_mJgQ%Z5{(DJ0NUSHE{9SW+sp+~)H$k#{uFBmI2puarZwDjj@*v*=3lCRZgHxFiAi-(J5~W+qfyS(grk5a`lz<&oE`lU1$qVx=dN} zw@_`lYoI1i4=SUTFdF?fS9G19w8&E3wx9a6JaYl#rrp-Gf#fnWy!-}@2g+*n%q7`r z0U95QXc(Iy1?5`WX{fc5_z1$N?@Q68>P3PQkAXG>h}4P}L^VcF=7S}j6rzYJA@5YX zpsuXXxxiWds7-9zg?ke4fG6b&b0_6Y_67c|;YIegpxI1iO0)hM@K*Aco;&BX(9&^% z`_=Mv+wp7wfx&Q~z~qg`70X64ulpU~7>#LQQy(OVEB!J1crwK&%M^(5+Oz_QyH1o# z9l~v64~ZBFA-RPEFmd*v)E#K7BH!1u-nj6YjaQ1hn~T=Hqw=D+*r#7xqJ3obqv9OF zmj$-$g&aQ;wJ5--cQ=YR!%>67 z_iFvm`=)3g0^w#4b8vR~k8@YWqN`@%G>(@bj2+m`9C4<0G(9`)&I1(Q{;nqThkf5| z^kdd1g!tW#-Zsdaln3gIsSBX;q_Dw(at2GqjRvxEe}tK)MpK?`=I(2eXfWH=`;HB|17WcQw$PP%%v~^xM3XGaqt4K<5c0R_J zd7KKbTFs>k%CTgh{eX?SX+ZQOC<$q>f?$o=Ac2DM31e3r8{wi4vN-l6Yo`+z9UHE# z&j@EHT7l{{IHe>wp&@RU>&(Q(J+-?pNolvrlZ)lFV^Ockd6+3Q@BR(vv3~rQ4cNuq z1YfR6nOsAh7nHKHTRl7gC3ZoZ4N2<`$c{Y^z~1DsXCk15alqNe(#yvnBL<<>w2&HP zX+u>NDTp;GVx!dFI(X@V+8v4Q_jEtZzzX_(b7~rylHYN)b$!Kvxfg@5*dII?XpvIi zd=-9*GP$e#Xphf;j&N* z6}i<1g5*BdNV+mq{SoeDP3FEIe-tQCZC;T5+mah9Jv{P_iBTFNCHv-dH7|Rwj`|oj z8jzq@a;u}mY}0jHcngxon;|py>0)W`W@&Y414%3C2wpOEO6E9`%(!e5?0j`UltbwY{|oi9($wj1)HC zIxEK)8p*-SZcws)3WSLwHW1K`anOrkp88oFGZt!}zOg~UmaoN^C70`!ed&F53DZi- zJjgIu-0$Mc4dh6GixH{q;j}xUN+S%#M2UFcCzlL=!Ve-3RxuM;&^sh!lTSbnB5kfp z;v_x6`iXc5yK#@w95t_(yw3CqH<%#oviz0^&A$&51In`qPkSNp| z^3p6VF2`BEhU)?GcTEfFZ4`?l*tgW=`jR6Ud|A`yDX>wF*Xw9zK^c+oDnUKPt(oJp zuw7{y>*s)LD~UZATJfNnjFqA=&8c9K2`rQ$?vf!n%YAz`O0*ZHtUWg|o*A1=>^w4_ zzeqr&dQ4ZG)=*vvR^WM6ABi`|ig-%`cQ|>Y0+Uv)oOLK(Nj3!|Ib`m#32I@KNm@Dv zQgST&HmeFGSb4bdaaA|{?R5EpSSycCG$GI*q8w)CrT45}?kFjHrP>sA+<|`JR zh3(Yge#xX_pNDSF5`SxB3z~%}@FLWU=zzPuH{J$)ZdnauoN3%uidohBPZp0lTge-R$?7*H)f1GagW(~jva@|2f^MVV|3q`S26<^E4n1Jj?1fK=-W)0q zP2~;PGjhU(&^l@!YWeZUAvN^}RR$!GTpzi|bt@uiGkq&@Jq!^l79i`_cjx(Oz}|@| z(UL}_f4!Af9;%89%w6Ys!LI`vem*{K!XOpnrm5{3nW{|Op(9{GNZhXfJ%=vftr3G6 zxTG6eRLnLW3TKD_YJN}Ji|Nu_;I;z$AA8sDhh6I;BGF|}zzJ?A|05%sL71{;`ZfeQ ze!G-g*RAvJCwYbWagUCIPfGmSd@(&v$xHijzu4Hy_uMt|p9h)UeSlCGg>)YH$GfEz zug1%KXR~E6{}hkN7I~PU3krOmg!=RqoJ*wAZ$B-gF$Bw@rfdSvJM7)ONC3EcRAtT# zIQKMJQhO!p96 z&}SGDN|s8DDHujjy6Tth=3VZC7_BsEZz=4MO<+AX71(KfKp%Kmk>5fyEo^o9+gzc& zW$GlqCCANM6mt0+xR4MrM6yQ6cc?k%uYa>@_l5u|yXcIUi3j0h%kR?tPf~y=g0Y}) zvmt9{NHDjhi)T1he*t1X+Tp!lq8b?Wk2=N&&ByCV= zgX61Re_#FhX+BSXn5(Qeyt3^)14t55E&A#DO$n}QJTEcC-M7CPod$A?KrVH2-c`U# zyCP+^MerIZ79`1d`yZwauK+ntUjMJ+H(A3#;!t4kBI}zRB_ECG`Sai$p?s8rs?hqk z*mh?FZ%Xe8r#?Fp#X)NYC>khQ0fb>Ooy&vhGu?d?ktTsacs`$;C}4VJFU^#?vDk(xj499qI$^(@B> zIdbLrzX2t)cx~?tKG!#X+u_m9_@z}XQ7lg0>~@G3bvx$)=QKL6GbA-c~al`-xUMIVx;YxqH9Lso4mUSN+LTm zUJI1dq%9QMA5kyw1pvGG$ulezLDzyyscL=@&y1}~Bw0$+7Pn-I9>%-jcTY1{k`&MF zzDc4IJaP40)FZ@bE3@=1{T7nXB`Kb?6E~PRc*6zhoPCxskym!(I+T$>H7%h6kJ;;U zO*Vw%pT`(Xe=E%Nu=U&e=)At;g$Wo}VXLGiNSIcg_vXjc&G&Dat9J4G{pxZ%QcgIG z3cQg0o}hcIA$U5E7>9%(yjjE?8$@3l_+82Ad}H(gsufI#CC@pH?|JWSc%K;C+qc)6 zk5iBr9r0Q8d~S63@y=^(AHF{cWWu9G56+I@6|s?bF?BUsSuBXyzO6cHKi|u5r_`TY z5}RbBL|7_{*D0eZg^@J7)eA<(tYbjI=&_{H=r4JqpIQkdfv)3)XQJ2&(nmG3{FY$C z&gjT!fuEsXJLxHx41kk-ELr3s4EC0o-#Bya>4zKyhll$fb5gPv(`q&yrPj$ zUxPAtAc)abxsSQPMiM>{_datyQg`Hy>oJ}Av_P)4_kMUG{raXRV)ff8`id(6q4FAL zzxU%kf$YM-${aQ zC%QXK;{JX;tHDi*aP-=x{OW(}MT-^ifFPGonwl&#` zVIa;03b)%2Nn0>7D))hzJFzQ*^4t&m2CFzK3m9*9yF5hIh|flnK_YCcQv*CI0mZ6W zi@R{2pdCN&L-p`#Hg)}| z3Y#s_=QdVeO+VRKgAuAGB1gS)G{Qw(^F4O{x6o{wF=J++Z8eB3gk zbo3t}CD9gIkim4F$@c14%V;v;jRf;9@Vo(wsOrHkk+9buU(h|g-~G)M>8IrWDTIJ4 zkWGDRe3)WkAR7|_C1_A!sas!-OU0i^s3)h6npFOs^KkKUc7!(Ma=mD&lT^l1=_+}Y zcA8+#=P-OGz@v~~NY6rTc5gCrajDy%C-Eqpe$4Ef zJMNB-zIvF^Kk7A5G#qXF>>Aw(!}r!UIrs|CQp)nH%jJ534~WhrmD@PTjrm)aeUp-r zv43%Q*nwCL*;5&QmU50*dCen9_wu$I;ityWgPfiGmec$D>sXa)5S(uQ76XzUuDV6GYKh`_A1_t;Pg5*jHiF%FNqCxw;=a zZY~b>RDv|whpUxb6%2l_1O1#VSwdF4E12ps7;skZ6>F$l{Eq3BvTd~n!CK19_Ds2Fhc@Vj`=g{Powm!fX-r-8L zioX$R97>PRT>EkWAI;vWJ>zaC>i!p)NFS>7#rU*=w#av@51&nZew)#R*j~LdIV>G` zF!k<0b~I=x(k`2dc3ikoW&VK_0;UPfQfp&klx!mBqcp7K5+ZhAW4b5Y*m59p;`Z}5 zfb#RdYL!HMc1oMykcv5%-2;0lO81r~S%@`N;X?p=>BZD1sw>Yhp=n{pp8}`f(F4}_ zIdWv?Szptd(&0yBi|wPxbCt4ECK)-oyLG)NnCmQnweNPy$El*j&i9_R{iV^d>S;pN znW@l3=Z!xw?fFe)Rhv(!TmD%-&lxE5 zfL5z_PoxK_!fhu!orO0(Ad5lJXGeoA$7Zl5%+GAE=f5`>1;WPNA?=7F@xko|U4PgIvMf zppB0HU)WA&(T|zulXiIp5_6@_B)(Gb>)op)y7Rw&Pjo>7)R3g`nqo2|MiQsAK7<$Y z-CV@O+Khtude^?cz$*PUhwRb}+~6F#<2$p7sTRrUfX07L)BBNmWQW^gB*OcV(ptNlQ{f{;&-BfL^hJy~E?-BxX`h)G`&f zvDKcLJr8&Sh(jFhJ(^t*_Euz}-S@92hy>wqDJ$#yh5FS z%sxElpYQ21+O4wBwuyjm{-6JZA{pKoB#wz-?nNM;CV&JR3Wk=$zTuWHuk zr;PKfb{~e{{%KbH^|qMjz8!*1law;ti^2_-GsW|ipy_7w$w1e{ni|mn(C3$uny@ziS;=dR>?4<8qQTQT8S&Klo3>O@gtN)SQ#>wvR~X&=N}H)*O48CI zcWKME0Y|Tn3ea;earPP4&x)#F`IzP0XV?kqdc*Gp=}b&pnwlf7i_T=*PfXjHn)JQM zxK;9Cpv&0m?L<7{-8j(AuO?PR zKYNeT>n{e>=nFI%rI%E7?ZC*@$=5wO{nr*=aav}T)znRXo&d7=WypKtE@504~tQNA(suJwF@Uz@YS)7}AfOz2{ z6A~-i$t5r)Yi|V9+gVvq6)Du_BMD8I`^0>8XG$o&`bpRfSon--T{G-6xGE`! zvTu3s-i@ut9P|e1Ul8xVea-T z23Qr7xHkZUXlfIM&``NQ$SUnMZM+D~G0;h{w})-ppuAPw1qZsM9nqbLt>!L!M_2=a zUEhcy)8PWxHprgSZFAz+OVIUZG1=6(SW@$D&C9nP{D36VA)O3T5u(K?OPLN2-rm_R zQl4@8V)+?n9E_x!iHiI(&fzn%rhOpX zidm7dA8%82jd`bS{C$2)=4pNNaT3zsE)6vA(jVl%>zdG(XtbL{4_Y`c=inY&lc798 z?t=UM8RvGwDIm-5cqHmGDKEgB|5z|U^rba%0S@UPss+AkJ)1M4-*H?uoe0=CVRryc zU7BeQg)}_|{e!4}`Bh7L5qu(JKlq|~p(gGFW`LOe(IR?zF>9?=cXe?txf8ktpXJB1 z_eA$T$37=}u8OGc)lMBZ!fbAeouYUhg@uXg%~a3e9B-UQ$Uc1%e2;1zF0JbHuk_3s z+&fuFty1OzW#6)OhSAb_WLffn=AKN=QBW=#e!!m9K!g0pCUSY(gZlBqZ8g~cy<-Zse37@FDDXeD{e!im{yn8KVqK#3o*~|6=gL;tvjHP|KK?QgOn@A z=E*6=5^H*LiK;%mI|sbwE7I*#X=sa;NNTd*4V#TdRD*3nH^x&mvu_~^4m1IQYB6$?I}GrXjP*|$|~SHl@ODR z%}7a2*fHPwj7Y;k%i5lN4=??tWWyE4p?N8?!gU^*Nrrz&INMa2ek$TU&+#SNY%sUs~z1phYo3&d|JOolngj;?&S3VB#679 zB7|(Zn~z>AR?Q!n3TpSU=GHT>*b9nVnTJKhMn0Iz_?6ho`4Z%td!qbI*buYZ?f_U& zWtr>UR~K)V>!;)Q7HW6mcHCV}#V%hDg)_MlVS^vHFVM2ysbVrD|0B+wp@< zx)k_hdpjg)^~|$mIQxUmTCZ-=`KxxpZqJ3Cs3xvgbMs&~HqG36hU{bY6AJ8YXe|$= zm(U3f)#tD^6~wdM@!}N%QvUtW0oVSW<0*97SHO(Sz~l%D*D=q*0$EOEb+_-$J04LU zCT?K%f))6Y%s+|3ho9kpi{xZUs`3lwT>Zp;WlLx8JnrPBJ%hr0IZ5`*)(u z>{Mys88Hr18%k|#ZLEl$zq2ykKj@PMm>5r;NM;7(=EFEB3y%;HNv%rHVVhfUws(cR zC~lI2S!G|CgcbEaNz~ssY5MRMP(w981edtN(DqBfbiB58M>c%O8T>bH$}@ODKVMkU zLGH}?b&$m@sT)XR+Dr{J7m!nSmiYz_ud3#MhTg&kO2|Q|o(xrUct8TrGv5sI1((DY z8&Q1*B<3w?nA&hWhU2_yoV_8R2qM+0{3fGXZ#QbScWtktmTyO|ykU@~VN0w5y}rmb zR}XH)fE%ZUWMgH3tqFaZ{mh;9%XoOWs*F0j$dzQ@!%Er%S@g=h${4}(l)XZ-(s?w9 z(xJ6*VN>YTv$bKTtLW;TL5y(J$j3*Xn>$C5>NEazT`2@&XSf_Z l(e0Tu*bBn{*FPi7&Fw`GG_^VkVUNWvFQXz|A!+*I{{X(9Q_cVY literal 0 HcmV?d00001 diff --git a/docs/root/source/installation/_static/arch.jpg b/docs/root/source/connecting/_static/arch.jpg similarity index 100% rename from docs/root/source/installation/_static/arch.jpg rename to docs/root/source/connecting/_static/arch.jpg diff --git a/docs/root/source/installation/_static/cc_escrow_execute_abort.png b/docs/root/source/connecting/_static/cc_escrow_execute_abort.png similarity index 100% rename from docs/root/source/installation/_static/cc_escrow_execute_abort.png rename to docs/root/source/connecting/_static/cc_escrow_execute_abort.png diff --git a/docs/root/source/installation/_static/models_diagrams.odg b/docs/root/source/connecting/_static/models_diagrams.odg similarity index 100% rename from docs/root/source/installation/_static/models_diagrams.odg rename to docs/root/source/connecting/_static/models_diagrams.odg diff --git a/docs/root/source/connecting/_static/mongodb_cloud_manager_1.png b/docs/root/source/connecting/_static/mongodb_cloud_manager_1.png new file mode 100644 index 0000000000000000000000000000000000000000..16073d6b370df4d22a9853a8e266eedf6b509044 GIT binary patch literal 12196 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 * `JavaScript / Node.js Driver `_ * `Java Driver `_ diff --git a/docs/root/source/installation/api/http-client-server-api.rst b/docs/root/source/connecting/http-client-server-api.rst similarity index 98% rename from docs/root/source/installation/api/http-client-server-api.rst rename to docs/root/source/connecting/http-client-server-api.rst index 528d240..372a850 100644 --- a/docs/root/source/installation/api/http-client-server-api.rst +++ b/docs/root/source/connecting/http-client-server-api.rst @@ -4,10 +4,9 @@ SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) Code is Apache-2.0 and docs are CC-BY-4.0 -.. _the-http-client-server-api: +HTTP Client-Server API +************************** -The HTTP Client-Server API -========================== This page assumes you already know an API Root URL for a Planetmint node or reverse proxy. @@ -18,10 +17,10 @@ If you set up a Planetmint node or reverse proxy yourself, and you're not sure what the API Root URL is, then see the last section of this page for help. -.. _planetmint-root-url: Planetmint Root URL -------------------- +=================== + If you send an HTTP GET request to the Planetmint Root URL e.g. ``http://localhost:9984`` @@ -34,10 +33,10 @@ with something like the following in the body: :language: http -.. _api-root-endpoint: - +.. _Api root endpoint: API Root Endpoint ------------------ +================= + If you send an HTTP GET request to the API Root Endpoint e.g. ``http://localhost:9984/api/v1/`` @@ -50,7 +49,8 @@ that allows you to discover the Planetmint API endpoints: Transactions Endpoint ---------------------- +===================== + .. note:: @@ -210,7 +210,8 @@ Transactions Endpoint Transaction Outputs -------------------- +=================== + The ``/api/v1/outputs`` endpoint returns transactions outputs filtered by a given public key, and optionally filtered to only include either spent or @@ -332,7 +333,8 @@ unspent outputs. Assets ------- +====== + .. note:: @@ -456,7 +458,8 @@ Assets Transaction Metadata --------------------- +==================== + .. note:: @@ -580,7 +583,8 @@ Transaction Metadata Validators --------------------- +========== + .. http:get:: /api/v1/validators @@ -624,7 +628,8 @@ Validators Blocks ------- +====== + .. http:get:: /api/v1/blocks/{block_height} @@ -701,7 +706,8 @@ Blocks .. _determining-the-api-root-url: Determining the API Root URL ----------------------------- +============================ + When you start Planetmint Server using ``planetmint start``, an HTTP API is exposed at some address. The default is: @@ -713,7 +719,7 @@ so you can access it from the same machine, but it won't be directly accessible from the outside world. (The outside world could connect via a SOCKS proxy or whatnot.) -The documentation about Planetmint Server :doc:`Configuration Settings <../../installation/node-setup/configuration>` +The documentation about Planetmint Server :doc:`Configuration Settings <../../node-setup/configuration>` has a section about how to set ``server.bind`` so as to make the HTTP API publicly accessible. diff --git a/docs/root/source/installation/api/http-samples/api-index-response.http b/docs/root/source/connecting/http-samples/api-index-response.http similarity index 85% rename from docs/root/source/installation/api/http-samples/api-index-response.http rename to docs/root/source/connecting/http-samples/api-index-response.http index 5f5b316..178f1fd 100644 --- a/docs/root/source/installation/api/http-samples/api-index-response.http +++ b/docs/root/source/connecting/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.3/http-client-server-api.html", + "docs": "https://docs.planetmint.com/projects/server/en/v0.9.9/http-client-server-api.html", "metadata": "/metadata/", "outputs": "/outputs/", "streamedblocks": "ws://localhost:9985/api/v1/streams/valid_blocks", diff --git a/docs/root/source/installation/api/http-samples/get-block-request.http b/docs/root/source/connecting/http-samples/get-block-request.http similarity index 100% rename from docs/root/source/installation/api/http-samples/get-block-request.http rename to docs/root/source/connecting/http-samples/get-block-request.http diff --git a/docs/root/source/installation/api/http-samples/get-block-response.http b/docs/root/source/connecting/http-samples/get-block-response.http similarity index 100% rename from docs/root/source/installation/api/http-samples/get-block-response.http rename to docs/root/source/connecting/http-samples/get-block-response.http diff --git a/docs/root/source/installation/api/http-samples/get-block-txid-request.http b/docs/root/source/connecting/http-samples/get-block-txid-request.http similarity index 100% rename from docs/root/source/installation/api/http-samples/get-block-txid-request.http rename to docs/root/source/connecting/http-samples/get-block-txid-request.http diff --git a/docs/root/source/installation/api/http-samples/get-block-txid-response.http b/docs/root/source/connecting/http-samples/get-block-txid-response.http similarity index 100% rename from docs/root/source/installation/api/http-samples/get-block-txid-response.http rename to docs/root/source/connecting/http-samples/get-block-txid-response.http diff --git a/docs/root/source/installation/api/http-samples/get-tx-by-asset-request.http b/docs/root/source/connecting/http-samples/get-tx-by-asset-request.http similarity index 100% rename from docs/root/source/installation/api/http-samples/get-tx-by-asset-request.http rename to docs/root/source/connecting/http-samples/get-tx-by-asset-request.http diff --git a/docs/root/source/installation/api/http-samples/get-tx-by-asset-response.http b/docs/root/source/connecting/http-samples/get-tx-by-asset-response.http similarity index 100% rename from docs/root/source/installation/api/http-samples/get-tx-by-asset-response.http rename to docs/root/source/connecting/http-samples/get-tx-by-asset-response.http diff --git a/docs/root/source/installation/api/http-samples/get-tx-id-request.http b/docs/root/source/connecting/http-samples/get-tx-id-request.http similarity index 100% rename from docs/root/source/installation/api/http-samples/get-tx-id-request.http rename to docs/root/source/connecting/http-samples/get-tx-id-request.http diff --git a/docs/root/source/installation/api/http-samples/get-tx-id-response.http b/docs/root/source/connecting/http-samples/get-tx-id-response.http similarity index 100% rename from docs/root/source/installation/api/http-samples/get-tx-id-response.http rename to docs/root/source/connecting/http-samples/get-tx-id-response.http diff --git a/docs/root/source/installation/api/http-samples/index-response.http b/docs/root/source/connecting/http-samples/index-response.http similarity index 82% rename from docs/root/source/installation/api/http-samples/index-response.http rename to docs/root/source/connecting/http-samples/index-response.http index 052741f..b960b69 100644 --- a/docs/root/source/installation/api/http-samples/index-response.http +++ b/docs/root/source/connecting/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.3/http-client-server-api.html", + "docs": "https://docs.planetmint.com/projects/server/en/v0.9.9/http-client-server-api.html", "metadata": "/api/v1/metadata/", "outputs": "/api/v1/outputs/", "streamedblocks": "ws://localhost:9985/api/v1/streams/valid_blocks", @@ -15,7 +15,7 @@ Content-Type: application/json "validators": "/api/v1/validators" } }, - "docs": "https://docs.planetmint.com/projects/server/en/v0.9.3/", + "docs": "https://docs.planetmint.com/projects/server/en/v0.9.9/", "software": "Planetmint", - "version": "0.9.3" + "version": "0.9.9" } diff --git a/docs/root/source/installation/api/http-samples/post-tx-request.http b/docs/root/source/connecting/http-samples/post-tx-request.http similarity index 100% rename from docs/root/source/installation/api/http-samples/post-tx-request.http rename to docs/root/source/connecting/http-samples/post-tx-request.http diff --git a/docs/root/source/installation/api/http-samples/post-tx-response.http b/docs/root/source/connecting/http-samples/post-tx-response.http similarity index 100% rename from docs/root/source/installation/api/http-samples/post-tx-response.http rename to docs/root/source/connecting/http-samples/post-tx-response.http diff --git a/docs/root/source/connecting/index.rst b/docs/root/source/connecting/index.rst new file mode 100644 index 0000000..cfc338e --- /dev/null +++ b/docs/root/source/connecting/index.rst @@ -0,0 +1,23 @@ + +.. 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 + +Connecting to Planetmint +######################## + +Planetmint enables you to connect to it via variaous ways: + +* Bindings or drivers for varioues languages exist +* RESTful APIs and direct database queries + +Details are listed below. + +.. include:: drivers.rst +.. include:: http-client-server-api.rst +.. include:: websocket-event-stream-api.rst +.. include:: query.rst +.. .. include:: api/index.rst +.. .. include:: commands-and-backend/index.rst + diff --git a/docs/root/source/query.rst b/docs/root/source/connecting/query.rst similarity index 98% rename from docs/root/source/query.rst rename to docs/root/source/connecting/query.rst index 821eeae..513bc18 100644 --- a/docs/root/source/query.rst +++ b/docs/root/source/connecting/query.rst @@ -4,14 +4,15 @@ SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) Code is Apache-2.0 and docs are CC-BY-4.0 -Queries in Planetmint -===================== +Database Queries +****************** A node operator can use the full power of MongoDB's query engine to search and query all stored data, including all transactions, assets and metadata. The node operator can decide for themselves how much of that query power they expose to external users. -Blog Post with Example Queries ------------------------------- +Querying MongoDB +============================== + We wrote a blog post in The Planetmint Blog to show how to use some MongoDB tools to query a Planetmint node's MongoDB database. @@ -22,6 +23,7 @@ about custom cars and their ownership histories. How to Connect to MongoDB ------------------------- + Before you can query a MongoDB database, you must connect to it, and to do that, you need to know its hostname and port. If you're running a Planetmint node on your local machine (e.g. for dev and test), then the hostname should be ``localhost`` and the port should be ``27017``, unless you did something to change those values. If you're running a Planetmint node on a remote machine and you can SSH to that machine, then the same is true. @@ -31,6 +33,7 @@ If you're running a Planetmint node on a remote machine and you configured its M How to Query ------------ + A Planetmint node operator has full access to their local MongoDB instance, so they can use any of MongoDB's APIs for running queries, including: - `the Mongo Shell `_, diff --git a/docs/root/source/installation/api/websocket-event-stream-api.rst b/docs/root/source/connecting/websocket-event-stream-api.rst similarity index 93% rename from docs/root/source/installation/api/websocket-event-stream-api.rst rename to docs/root/source/connecting/websocket-event-stream-api.rst index 96eab15..c754b0c 100644 --- a/docs/root/source/installation/api/websocket-event-stream-api.rst +++ b/docs/root/source/connecting/websocket-event-stream-api.rst @@ -6,8 +6,9 @@ .. _the-websocket-event-stream-api: -The WebSocket Event Stream API -============================== +WebSocket Event Stream API +****************************** + .. important:: The WebSocket Event Stream runs on a different port than the Web API. The @@ -21,18 +22,20 @@ to notify you as events occur, such as new `valid transactions <#valid-transacti Demoing the API ---------------- +=============== + You may be interested in demoing the Event Stream API with the `WebSocket echo test `_ to familiarize yourself before attempting an integration. Determining Support for the Event Stream API --------------------------------------------- +============================================ + It's a good idea to make sure that the node you're connecting with has advertised support for the Event Stream API. To do so, send a HTTP GET -request to the node's :ref:`api-root-endpoint` +request to the node's `API root endpoint`_ (e.g. ``http://localhost:9984/api/v1/``) and check that the response contains a ``streams`` property: @@ -46,7 +49,8 @@ response contains a ``streams`` property: Connection Keep-Alive ---------------------- +===================== + The Event Stream API supports Ping/Pong frames as descibed in `RFC 6455 `_. @@ -58,7 +62,8 @@ The Event Stream API supports Ping/Pong frames as descibed in same. Streams -------- +======= + Each stream is meant as a unidirectional communication channel, where the Planetmint node is the only party sending messages. Any messages sent to the @@ -85,7 +90,8 @@ All messages sent in a stream are in the JSON format. API, consider creating a new `BEP `_. Valid Transactions -~~~~~~~~~~~~~~~~~~ +================== + ``/valid_transactions`` diff --git a/planetmint/migrations/__init__.py b/docs/root/source/cryptoconditions.md similarity index 100% rename from planetmint/migrations/__init__.py rename to docs/root/source/cryptoconditions.md diff --git a/docs/root/source/index.rst b/docs/root/source/index.rst index 5a013c8..8c2d0b6 100644 --- a/docs/root/source/index.rst +++ b/docs/root/source/index.rst @@ -4,8 +4,8 @@ SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) Code is Apache-2.0 and docs are CC-BY-4.0 -Planetmint Documentation -======================== +Planetmint +========== Meet Planetmint. The blockchain database. @@ -14,20 +14,23 @@ including decentralization, immutability and native support for assets. At a high level, one can communicate with a Planetmint network (set of nodes) using the Planetmint HTTP API, or a wrapper for that API, such as the Planetmint Python Driver. Each Planetmint node runs Planetmint Server and various other software. The `terminology page `_ explains some of those terms in more detail. -More About Planetmint ---------------------- +.. toctree:: + :maxdepth: 3 + + Introdcution + Using Planetmint + Node Setup + Networks & Federations + Connecting to Planetmint + tools/index + contributing/index + terminology + troubleshooting + .. toctree:: :maxdepth: 1 + :caption: Cryptoconditions & Smart Contracts - Planetmint Docs Home - about-planetmint - terminology - properties - basic-usage - installation/index - drivers/index - query - contributing/index - korean/index - + Crypto Conditions & Smart Contracts + cryptoconditions diff --git a/docs/root/source/installation/api/index.rst b/docs/root/source/installation/api/index.rst deleted file mode 100644 index 7693fab..0000000 --- a/docs/root/source/installation/api/index.rst +++ /dev/null @@ -1,16 +0,0 @@ - -.. 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 - - - -API -=== - -.. toctree:: - :maxdepth: 1 - - http-client-server-api - websocket-event-stream-api diff --git a/docs/root/source/installation/index.rst b/docs/root/source/installation/index.rst deleted file mode 100644 index 2efc18a..0000000 --- a/docs/root/source/installation/index.rst +++ /dev/null @@ -1,20 +0,0 @@ - -.. 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 - -Installation -============ - -You can install a single node to test out Planetmint, connect it to a network or setup a network of nodes. - -.. toctree:: - :maxdepth: 1 - - quickstart - node-setup/index - network-setup/index - api/index - commands-and-backend/index - appendices/index diff --git a/docs/root/source/installation/node-setup/index.rst b/docs/root/source/installation/node-setup/index.rst deleted file mode 100644 index e7efc00..0000000 --- a/docs/root/source/installation/node-setup/index.rst +++ /dev/null @@ -1,25 +0,0 @@ - -.. 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 - -Node setup -========== - -You can use the all-in-one docker solution, or install Tendermint, MongoDB, and Planetmint step by step. For more advanced users and for development, the second option is recommended. - -.. toctree:: - :maxdepth: 1 - - deploy-a-machine - aws-setup - all-in-one-planetmint - planetmint-node-ansible - set-up-node-software - set-up-nginx - configuration - planetmint-cli - troubleshooting - production-node/index - release-notes diff --git a/docs/root/source/installation/node-setup/production-node/index.rst b/docs/root/source/installation/node-setup/production-node/index.rst deleted file mode 100644 index 2b1300e..0000000 --- a/docs/root/source/installation/node-setup/production-node/index.rst +++ /dev/null @@ -1,17 +0,0 @@ - -.. 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 - -Production Nodes -================ - -.. toctree:: - :maxdepth: 1 - - node-requirements - node-assumptions - node-components - node-security-and-privacy - reverse-proxy-notes diff --git a/docs/root/source/installation/node-setup/release-notes.md b/docs/root/source/installation/node-setup/release-notes.md deleted file mode 100644 index 4dfbe1c..0000000 --- a/docs/root/source/installation/node-setup/release-notes.md +++ /dev/null @@ -1,16 +0,0 @@ - - -# Release Notes - -You can find a list of all Planetmint Server releases and release notes on GitHub at: - -[https://github.com/planetmint/planetmint/releases](https://github.com/planetmint/planetmint/releases) - -The [CHANGELOG.md file](https://github.com/planetmint/planetmint/blob/master/CHANGELOG.md) contains much the same information, but it also has notes about what to expect in the _next_ release. - -We also have [a roadmap document in ROADMAP.md](https://github.com/planetmint/org/blob/master/ROADMAP.md). diff --git a/docs/root/source/installation/quickstart.md b/docs/root/source/installation/quickstart.md deleted file mode 100644 index fa58301..0000000 --- a/docs/root/source/installation/quickstart.md +++ /dev/null @@ -1,91 +0,0 @@ - - - -# Introduction - -This is the documentation for Planetmint Server, or in other words, node - -the Planetmint software that is on servers (but not on clients). - -## Setup Instructions for Various Cases - -- Quickstart link below -- [Set up a local Planetmint node for development, experimenting and testing](node-setup/index) -- [Set up and run a Planetmint network](network-setup/index) - -## Develop an App Test - -To develop an app that talks to a Planetmint network, you'll want a test network to test it against. You have a few options: - -1. The IPDB Test Network (or "Testnet") is a free-to-use, publicly-available test network that you can test against. It is available at [IPDB testnet](https://test.ipdb.io/). -1. You could also run a Planetmint node on you local machine. One way is to use this node setup guide with a one-node "network" by using the all-in-one docker solution, or manual installation and configuration of the components. Another way is to use one of the deployment methods listed in the [network setup guide](network-setup/index) or in the [the docs about contributing to Planetmint](../contributing/index). - - -## (WIP) Quickstart - - - -## Try Planetmint - -Create a transaction and post it to the test network: - - - -
- -
-
-
-
-
-
-
-
-
-
-
-
diff --git a/docs/root/source/about-planetmint.rst b/docs/root/source/introduction/about-planetmint.rst similarity index 91% rename from docs/root/source/about-planetmint.rst rename to docs/root/source/introduction/about-planetmint.rst index d693992..6cebc74 100644 --- a/docs/root/source/about-planetmint.rst +++ b/docs/root/source/introduction/about-planetmint.rst @@ -4,24 +4,24 @@ SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) Code is Apache-2.0 and docs are CC-BY-4.0 -About Planetmint ----------------- +What is Planetmint +================== Basic Facts -=========== +----------- -#. One can store arbitrary data (including encrypted data) in a Planetmint network, within limits: there’s a maximum transaction size. Every transaction has a ``metadata`` section which can store almost any Unicode string (up to some maximum length). Similarly, every CREATE transaction has an ``asset.data`` section which can store almost any Unicode string. -#. The data stored in certain Planetmint transaction fields must not be encrypted, e.g. public keys and amounts. Planetmint doesn’t offer private transactions akin to Zcoin. -#. Once data has been stored in a Planetmint network, it’s best to assume it can’t be change or deleted. -#. Every node in a Planetmint network has a full copy of all the stored data. -#. Every node in a Planetmint network can read all the stored data. -#. Everyone with full access to a Planetmint node (e.g. the sysadmin of a node) can read all the data stored on that node. -#. Everyone given access to a node via the Planetmint HTTP API can find and read all the data stored by Planetmint. The list of people with access might be quite short. -#. If the connection between an external user and a Planetmint node isn’t encrypted (using HTTPS, for example), then a wiretapper can read all HTTP requests and responses in transit. -#. If someone gets access to plaintext (regardless of where they got it), then they can (in principle) share it with the whole world. One can make it difficult for them to do that, e.g. if it is a lot of data and they only get access inside a secure room where they are searched as they leave the room. +1. One can store arbitrary data (including encrypted data) in a Planetmint network, within limits: there’s a maximum transaction size. Every transaction has a ``metadata`` section which can store almost any Unicode string (up to some maximum length). Similarly, every CREATE transaction has an ``asset.data`` section which can store almost any Unicode string. +2. . The data stored in certain Planetmint transaction fields must not be encrypted, e.g. public keys and amounts. Planetmint doesn’t offer private transactions akin to Zcoin. +3. Once data has been stored in a Planetmint network, it’s best to assume it can’t be change or deleted. +4. Every node in a Planetmint network has a full copy of all the stored data. +5. Every node in a Planetmint network can read all the stored data. +6. Everyone with full access to a Planetmint node (e.g. the sysadmin of a node) can read all the data stored on that node. +7. Everyone given access to a node via the Planetmint HTTP API can find and read all the data stored by Planetmint. The list of people with access might be quite short. +8. If the connection between an external user and a Planetmint node isn’t encrypted (using HTTPS, for example), then a wiretapper can read all HTTP requests and responses in transit. +9. If someone gets access to plaintext (regardless of where they got it), then they can (in principle) share it with the whole world. One can make it difficult for them to do that, e.g. if it is a lot of data and they only get access inside a secure room where they are searched as they leave the room. Planetmint for Asset Registrations & Transfers -============================================== +---------------------------------------------- Planetmint can store data of any kind, but it's designed to be particularly good for storing asset registrations and transfers: @@ -37,7 +37,8 @@ Planetmint can store data of any kind, but it's designed to be particularly good We used the word "owners" somewhat loosely above. A more accurate word might be fulfillers, signers, controllers, or transfer-enablers. See the section titled **A Note about Owners** in the relevant `Planetmint Transactions Spec `_. -# Production-Ready? +Production-Ready? +----------------- Depending on your use case, Planetmint may or may not be production-ready. You should ask your service provider. If you want to go live (into production) with Planetmint, please consult with your service provider. @@ -45,7 +46,7 @@ If you want to go live (into production) with Planetmint, please consult with yo Note: Planetmint has an open source license with a "no warranty" section that is typical of open source licenses. This is standard in the software industry. For example, the Linux kernel is used in production by billions of machines even though its license includes a "no warranty" section. Warranties are usually provided above the level of the software license, by service providers. Storing Private Data Off-Chain -============================== +------------------------------ A system could store data off-chain, e.g. in a third-party database, document store, or content management system (CMS) and it could use Planetmint to: @@ -67,7 +68,7 @@ There are other ways to accomplish the same thing. The above is just one example You might have noticed that the above example didn’t treat the “read permission” as an asset owned (controlled) by a user because if the permission asset is given to (transferred to or created by) the user then it cannot be controlled any further (by DocPile) until the user transfers it back to DocPile. Moreover, the user could transfer the asset to someone else, which might be problematic. Storing Private Data On-Chain, Encrypted -======================================== +----------------------------------------- There are many ways to store private data on-chain, encrypted. Every use case has its own objectives and constraints, and the best solution depends on the use case. `The IPDB consulting team `_ can help you design the best solution for your use case. diff --git a/docs/root/source/introduction/index.rst b/docs/root/source/introduction/index.rst new file mode 100644 index 0000000..e3254d9 --- /dev/null +++ b/docs/root/source/introduction/index.rst @@ -0,0 +1,10 @@ +Introduction +############ + +.. include:: quickstart.md + :parser: myst_parser.sphinx_ +.. include:: about-planetmint.rst + :parser: myst_parser.sphinx_ +.. include:: properties.md + :parser: myst_parser.sphinx_ + diff --git a/docs/root/source/properties.md b/docs/root/source/introduction/properties.md similarity index 99% rename from docs/root/source/properties.md rename to docs/root/source/introduction/properties.md index 861fe0a..8610082 100644 --- a/docs/root/source/properties.md +++ b/docs/root/source/introduction/properties.md @@ -5,7 +5,7 @@ SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) Code is Apache-2.0 and docs are CC-BY-4.0 ---> -# Properties of Planetmint +# Properties of Planetmint ## Decentralization diff --git a/docs/root/source/introduction/quickstart.md b/docs/root/source/introduction/quickstart.md new file mode 100644 index 0000000..ffa4f28 --- /dev/null +++ b/docs/root/source/introduction/quickstart.md @@ -0,0 +1,80 @@ + + + + +# Quickstart +Planetmint is a metadata blockchain. This introduction gives an overview about how to attest data to Planetmint. First, simple transaction creation and sending is shown. Thereafter, an introdcution about how to set up a single node or a cluster is given. + + + +## The IPDB Testnet - sending transactions +The IPDB foundation hosta a testnet server that is reset every night at 4am UTC. + +The following sequence shows a simple asset notarization / attestion on that testnet: +Create a file named notarize.py + +``` +from planetmint_driver import Planetmint +from planetmint_driver.crypto import generate_keypair + +plntmnt = Planetmint('https://test.ipdb.io') +alice = generate_keypair() +tx = plntmnt.transactions.prepare( + operation='CREATE', + signers=alice.public_key, + asset={'data': {'message': 'Blockchain all the things!'}}) +signed_tx = plntmnt.transactions.fulfill( + tx, + private_keys=alice.private_key) +plntmnt.transactions.send_commit(signed_tx) +``` + +install dependencies and execute it + +``` +$ pip install planetmint-driver +$ python notarize.py +``` +# Install Planetmint +## Local Node +Planemtint is a Tendermint applicatoin with an attached database. +A basic installation installs the database, Tenermint and therafter Planetmint. + +The instalation of the database is as follows: +``` +$ sudo apt install mongodb +``` +Tendermint can be installed and started as follows +``` +$ wget https://github.com/tendermint/tendermint/releases/download/v0.34.15/tendermint_0.34.15_linux_amd64.tar.gz +$ tar zxf tendermint_0.34.15_linux_amd64.tar.gz +$ ./tendermint init +$ ./tendermint node --proxy_app=tcp://localhost:26658 +``` +Planetmint installs and starts as described below +``` +$ pip install planetmint +$ planetmint configure +$ planetmint start +``` + +## Cluster of nodes +Setting up a cluster of nodes comes down to set up a cluster of tendermint nodes as documented at [Tendermint](https://docs.tendermint.com/v0.35/introduction/quick-start.html#cluster-of-nodes). In addition to that, the database and Planetmint need to be installed on the servers as described above. + +## Setup Instructions for Various Cases + +- Quickstart link below +- [Set up a local Planetmint node for development, experimenting and testing](../node-setup/index) +- [Set up and run a Planetmint network](../network-setup/index) + +## Develop an App Test + +To develop an app that talks to a Planetmint network, you'll want a test network to test it against. You have a few options: + +1. The IPDB Test Network (or "Testnet") is a free-to-use, publicly-available test network that you can test against. It is available at [IPDB testnet](https://test.ipdb.io/). +1. You could also run a Planetmint node on you local machine. One way is to use this node setup guide with a one-node "network" by using the all-in-one docker solution, or manual installation and configuration of the components. Another way is to use one of the deployment methods listed in the [network setup guide](../network-setup/index) or in the [the docs about contributing to Planetmint](../contributing/index). diff --git a/docs/root/source/korean/_static/CREATE_and_TRANSFER_example.png b/docs/root/source/korean/_static/CREATE_and_TRANSFER_example.png deleted file mode 100644 index f9ef1ee07e01f4c24831228ff0b7e4d089c398f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4149 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

    ^E$mmf?g|6k4xy+avDsj!-T$t7RC`R zneZvFjZz)NX7Yq8OwKh(oQqK{8=^%A22m-^z?nTq)C|9ca`uzo^rAW*$9~NzqSU%; zy)0(wX+4@kcUkSxy~bu~cW{(DIbca99k2K6_d_VPBog+bKK^-%(R9mYnBY45tQ20) zhiB~e-SBjEAD$H&M@>^-E)RGKhsJoJpd;Z4Ke2Uc-$ z14~}llkD_yl7@`LR#08#ugNhtd{mzk&c*7nq18sZ%usgHg9*jURSUJsw7M+RAAoOA z7lnag($@$I&&{qyevc;#$uU!GjG-1wh$b%st<)CTK$y4E#i4(m1IjE zV7^v_F@;#1H55h%%>26$y246=&c+02k$%#*AzHNOLLC7$$QUfU8mQQ4sG%r+3SGN*2-x>Z7pZxT&Q}9qMx@q6y zin1o?f8YNk@IMLsPXhmw!2cxh|APdOR@dw|Ig+X_lLROo2mk=6s;g4@w{7_U0Wns< AF8}}l diff --git a/docs/root/source/korean/assets_ko.md b/docs/root/source/korean/assets_ko.md deleted file mode 100644 index a4508ab..0000000 --- a/docs/root/source/korean/assets_ko.md +++ /dev/null @@ -1,21 +0,0 @@ - - - -Planetmint가 자산 등록 및 전송에 적합한 방법 -========================================================== - -Planetmint는 모든 종류의 데이터를 저장할 수 있지만 자산 등록 및 전송을 저장하는 데 특히 유용합니다.: - -* Planetmint 네트워크에 전송되어 체크되고 저장되는 (있는 경우) 트랜잭션은 기본적으로 CREATE 트랜잭션과 TRANSFER 트랜잭션의 두 가지가 있습니다. -* CREATE 트랜잭션은 임의의 메타 데이터와 함께 모든 종류의 자산 (나눌 수 없거나 분할 할 수없는)을 등록하는 데 사용할 수 있습니다. -* 저작물에는 0 명, 1 명 또는 여러 명의 소유자가있을 수 있습니다. -* 자산 소유자는 자산을 신규 소유자에게 양도하려는 사람이 만족해야하는 조건을 지정할 수 있습니다. 예를 들어 5 명의 현재 소유자 중 최소 3 명이 TRANSFER 트랜잭션에 암호를 사용해야합니다. -* Planetmint는 TRANSFER 트랜잭션의 유효성을 검사하는 과정에서 조건이 충족되었는지 확인합니다. (또한 누구나 만족하는지 확인할 수 있습니다.) -* Planetmint는 자산의 이중 지출을 방지합니다. -* 유효성이 검증 된 트랜잭션은 [변경불가능](https://github.com/planetmint/planetmint/blob/master/docs/root/source/korean/immutable-ko.md) 입니다. - - Note - - 우리는 "소유자"라는 단어를 다소 느슨하게 사용했습니다. **보다 정확한 단어**는 이행자, 서명자, 조정자 또는 이전 가능 요소 일 수 있습니다. 관련 [Planetmint Transaction Spec](https://github.com/planetmint/BEPs/tree/master/tx-specs/)의 Owners에 대한 참고 사항 절을 참조하십시오. diff --git a/docs/root/source/korean/bft-ko.md b/docs/root/source/korean/bft-ko.md deleted file mode 100644 index fe1123e..0000000 --- a/docs/root/source/korean/bft-ko.md +++ /dev/null @@ -1,13 +0,0 @@ - - -# Planetmint와 Byzantine Fault Tolerance - -[Planetmint Server](https://docs.planetmint.io/projects/server/en/latest/index.html) -는 블록체인 합의와 트랜잭션 복제에 [Tendermint](https://tendermint.io/)를 사용합니다. - -그리고 Tendermint 는 [Byzantine Fault Tolerant (BFT)](https://en.wikipedia.org/wiki/Byzantine_fault_tolerance). diff --git a/docs/root/source/korean/decentralized_kor.md b/docs/root/source/korean/decentralized_kor.md deleted file mode 100644 index 173e9c3..0000000 --- a/docs/root/source/korean/decentralized_kor.md +++ /dev/null @@ -1,24 +0,0 @@ - - -# Planetmint 분산 방식 - -분산이란 모든 것을 소유하거나 통제하는 사람이 없고, 단 하나의 실패 지점이 없다는 것을 의미합니다. - -이상적으로, Planetmint 네트워크에서 각각의 노드는 다른 개인 또는 조직에 의해 소유되고 관리됩니다. 네트워크가 한 조직 내에 상주하고 있더라도, 각 노드를 다른 사용자나 부서로 제어하는 것이 좋습니다. - -우리는 "Planetmint 컨소시엄" (또는 단지 "컨소시엄")은 Planetmint 네트워크의 노드를 구동하는 사람들 혹은 조직을 의미합니다. 컨소시엄은 회원제나 정책과 같은 결정을 내리기 위한 어떠한 형태의 거버넌스 요구합니다. 거버넌스 프로세스의 정확한 세부사항은 각 컨소시엄에 의해 결정되지만, 상당히 분산될 수 있습니다. - -컨소시엄은 관할의 다양성과 지리적 다양성 및 기타 종류의 다양성을 증가시킴으로써 분산화(및 탄력성)를 증가시킬 수 있습니다. 이 아이디어는 [노드 다양성 부문](diversity-ko)에서 확장됩니다. - -Planetmint 네트워크에는 오래된 특정한 위치를 가지는 노드가 없습니다. 모든 노드들은 동일한 소프트웨어를 실행하고 동일한 작업을 수행합니다. - -만약 노드에 대한 관리자 접근 권한이 있는 경우, 해당 노드를 제거할 수 있지만(예: 해당 노드에 저장된 데이터 변경 또는 삭제), 이러한 변경 사항은 해당 노드에 분리된 상태로 유지되어야 합니다. Planetmint 네트워크는 노드의 3분의 1 이상이 손상된 경우에만 손상될 수 있습니다. 자세한 내용은 [Tendermint 문서](https://tendermint.io/docs/introduction/introduction.html)을 참조하십시오. - -노드의 관리자나 슈퍼 유저도 자산을 전송할 수 없다는 점에 유의하십시오. 유효한 전송 트랜잭션을 생성하는 유일한 방법은 자산에 대한 현재 암호화 조건을 충족하는 것이며 관리자/슈퍼사용자는 필요한 정보를 가지고 있지 않기 때문에 이 작업을 수행할 수 없습니다(예: 개인 키). - -노드의 관리자나 슈퍼 사용자도 자산을 전송할 수는 없다는 점을 유의하십시오. 타당한 전송 트랜잭션을 만드는 유일한 방법은 자산에 대한 현재 암호화 조건을 충족시키는 것이며, 관리자 또는 슈퍼 사용자는 필요한 정보를 가지고 있지 않기 때문에 이 작업을 수행할 수 없습니다. (예: 개인 키) \ No newline at end of file diff --git a/docs/root/source/korean/diversity-ko.md b/docs/root/source/korean/diversity-ko.md deleted file mode 100644 index 3b5b499..0000000 --- a/docs/root/source/korean/diversity-ko.md +++ /dev/null @@ -1,18 +0,0 @@ - - -# 노드 다양성의 종류 - - -한 명의 유저나 이벤트가 노드의 "상당수" 부분을 제어하거나 손상시키는 것을 어렵게 만드는 절차가 수행되어야 합니다.(Planetmint 서버는 Tendermint를 사용하기 때문에 여기서 "상당수"는 노드의 1/3을 말합니다.) 아래에 쓰여진 여러 가지의 다양성을 고려해야 합니다. 모든 종류에 있어서 높은 다양성을 갖는 것은 꽤 어려운 일입니다. - -1. **관할권 다양성.** 노드는 여러 합법적 관할권 내의 참여자들이 제어합니다. 이는 어떤 일을 수행하기에 이 수단들을 사용하기 어렵게 할 수 있습니다. -1. **지리적 다양성.** 서버는 지리적으로 여러 곳에 물리적으로 위치합니다. 이는 자연 재해(홍수 또는 지진 등)가 문제를 야기할 만큼 손상되기 어렵게 합니다. -1. **호스팅 다양성.** 서버는 여러 호스팅 공급자(ex. Amazon Web Services, Microsoft Azure, Digital Oceure, Rackspace)가 호스팅해야 합니다. 이는 하나의 호스팅 공급자가 '상당 수'의 노드에 영향을 미치기가 어려워집니다. -1. **일반적인 의미의 다양성.** 일반적으로 멤버십 다양성은 컨소시엄에 많은 이점을 줍니다. 예를 들어, 그것은 문제 해결에 필요한 다양한 아이디어 소스를 컨소시엄에 제공합니다. - -참고: 모든 노드가 동일한 코드(ex. Planetmint의 동일한 구현)를 실행하고 있는 경우, 해당 코드의 버그를 사용하여 모든 노드를 손상시킬 수 있습니다. 이상적으로는 Planetmint 서버(예: Python, Go 등)에서 운영되고 있는 다양한 구현이 있어, 컨소시엄은 다양한 서버 구현을 할 수 있을 것입니다. 운영 체제에 대해서도 이는 유사하게 적용됩니다. diff --git a/docs/root/source/korean/immutable-ko.md b/docs/root/source/korean/immutable-ko.md deleted file mode 100644 index 5bcaac4..0000000 --- a/docs/root/source/korean/immutable-ko.md +++ /dev/null @@ -1,27 +0,0 @@ - - -# 어떻게 Planetmint는 불변성을 갖는가 - -*Imunable*이라는 단어는 "시간 경과에 따른 불변성"을 의미합니다. 예를 들어, π의 10진수 값은 변경할 수 없습니다(3.14159...). - -블록체인 커뮤니티는 종종 블록체인을 "불변하다"고 설명합니다. 우리가 그 단어를 문자 그대로 해석한다면, 그것은 블록체인 데이터가 변경할 수 없거나 영구적이라는 것을 의미하는데, 이것은 말이 안됩니다. 데이터는 *변경 될 수 있습니다.* 예를 들어, 전염병이 인류를 멸종 시킬 수도 있는 것처럼 데이터는 수분에 의한 손상, 온도에 의한 손상, 엔트로피의 일반적인 증가로 인해 시간이 지남에 따라 손상될 수 있습니다. - -블록체인 데이터가 일반적인 경우보다 변경(혹은 삭제)하기가 더 어려운 것은 사실입니다. 블록체인 데이터는 단순히 (의도적인) "변형 방지"에 그치지 않고 하드 드라이브의 데이터 손상과 같은 비의도적으로 발생할 수 있는 무작위 변경에도 대응합니다. 따라서 블록체인에서 "불변한다"라는 단어를 우리는 어떤 모든 의도와 목적이 *실제적으로* 불변한 것으로 해석합니다. (언어학자들은 "불변한다"라는 단어가 블록체인 커뮤니티에서 쓰이는 *기술적 용어*라고 말할 것입니다.) - -블록체인 데이터는 여러 가지 방법을 통해 불변성을 가질 수 있습니다: - -1. **데이터 변경 또는 삭제를 위한 API 없음.** Blockchain 소프트웨어는 일반적으로 블록체인에 저장된 데이터를 변경하거나 삭제하기 위한 API를 제공하지 않습니다. Planetmint 역시 관련한 API를 제공하지 않습니다 . 이것은 변경이나 삭제가 *다른 방식*으로 일어나는 것을 막지 못합니다. 이것은 단지 하나의 방어선일 뿐입니다. -1. **복제.** 모든 데이터는 여러 곳에 복제(복사)됩니다. 복제 팩터가 높을수록, 모든 복제본을 변경하거나 삭제하기가 더 어려워집니다. -1. **내부 감시 장치.** 모든 노드가 모든 변경 사항을 모니터링하고 허용되지 않은 변경 사항이 발생하면 적절한 조치를 취할 수 있습니다. -1. **외부 감시 장치.** 컨소시엄은 부정행위를 찾아 데이터를 모니터링하고 감사할 수 있는 검증된 제 3자를 선택할 수 있습니다. 공개적으로 읽을 수 있는 데이터를 가진 컨소시엄의 경우, 대중은 감사자 역할을 할 수 있습니다. -1. **경제적 인센티브.** 일부 블록체인 시스템은 기존의 저장된 데이터를 변경하는 데 많은 비용이 들게 합니다. 그 예로 작업 증명 및 지분 증명 시스템이 있습니다. Planetmint의 경우에는 이런 인센티브를 사용하지 않습니다. -1. 변화에 대한 손쉬운 실행 취소를 위해 오류 수정 코드와 같은 고급 기술을 사용해 데이터를 저장할 수 있습니다 -1. **암호화폐의 표식**은 종종 메시지(예: 트랜잭션)가 도중에 손상되었는지 확인하고 메시지에 서명한 사용자를 확인하는 방법으로 사용됩니다. Planetmint에서는 각 트랜잭션에 한 명 이상의 당사자가 서명해야 합니다 -1. **전체 또는 부분적 백업**은 때때로 자기 테이프 저장소, 기타 블록체인, 인쇄물 등에 기록될 수 있습니다. -1. **강력한 보안** 노드 소유자는 강력한 보안 정책을 채택하고 적용할 수 있습니다. -1. **노드 다양성.** 다양성으로 인해서 한 가지 요소(예: 자연 재해 또는 운영 체제 버그)가 상당 수의 노드를 손상시킬 수 없도록 합니다. [노드 다양성의 종류에 대한 섹션](https://github.com/planetmint/planetmint/blob/master/docs/root/source/korean/diversity-ko.md)을 참조하세요. diff --git a/docs/root/source/korean/index.rst b/docs/root/source/korean/index.rst deleted file mode 100644 index 101254c..0000000 --- a/docs/root/source/korean/index.rst +++ /dev/null @@ -1,98 +0,0 @@ - -.. 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 - -Planetmint 문서 -======================== - -블록체인 데이터베이스인 Planetmint를 만나보세요. - -`분산형 `_, `불변성 `_ 및 `자산에 대한 네이티브 지원 `_ 을 포함한 일부 데이터베이스 특성들과 블록체인 특성을 가지고 있습니다. - -높은 수준에서, 사용자는 Planetmint HTTP API를 사용하는 Planetmint 네트워크(노드 집합) 또는 Planetmint 파이썬 드라이버와 같은 API용 래퍼로 통신할 수 있습니다. 각 Planetmint 노드는 Planetmint Server 및 다양한 다른 소프트웨어를 실행합니다. 더 자세한 사항은 용어 페이지에서 이러한 용어 중 일부를 설명합니다. - -.. raw:: html - - - -

    - - - - - - - -Planetmint에 대한 추가 정보 -------------------------------------------------------- - -.. toctree:: - :maxdepth: 1 - - Planetmint Docs Home - production-ready_kor - terminology_kor - decentralized_kor - diversity-ko - immutable-ko - bft-ko - query-ko - assets_ko - smart-contracts_ko - transaction-concepts_ko - store-files_ko - permissions-ko - private-data-ko - Data Models diff --git a/docs/root/source/korean/permissions-ko.md b/docs/root/source/korean/permissions-ko.md deleted file mode 100644 index 52f4e18..0000000 --- a/docs/root/source/korean/permissions-ko.md +++ /dev/null @@ -1,59 +0,0 @@ - - -# Planetmint 사용 권한 - -Planetmint를 사용하면 다른 사용자가 할 수 있는 것을 어느 정도 제어할 수 있습니다. -이 능력은 \*nix환경에서의 "권한", SQL에서의 "특권", 보안 환경에서의 "액세스 제어"와 유사합니다. - -## 출력 지출/이전 권한 - -Planetmint에서, 모든 출력에는 연관된 조건(crypto-condition)이 있습니다. - -사용되지 않은 출력을 쓰거나 전송하려면, 사용자(또는 사용자 그룹)이 조건을 충족시켜야 합니다. -특정 사용자만이 출력을 보낼 권한이 있다는 뜻입니다. 가장 단순한 조건은, "공용 키에 해당하는 개인 키를 가진 사람만이 출력을 보낼 수 있습니다." 훨씬 더 정교한 조건들도 가능합니다, 예를 들어 “이 출력을 사용하려면,…" - -- "…회계 그룹의 모든 사람이 서명 할 수 있습니다." -- "…네 명 중 세 명이 서명해야 합니다." -- "…Bob이 반드시 서명해야 하거나 Tom과 Sylvia 둘 모두가 서명해야 합니다." - -자세한 내용은, [Planetmint Transactions Spec](https://github.com/planetmint/BEPs/tree/master/tx-specs/)관련 **트랜잭션 구성요소:조건** 섹션을 참조하세요. - -출력이 한번 소비되면 다시 사용할 수 없습니다: *아무도* 그렇게 할 권한이 없습니다. 즉, Planetmint는 누구나 출력을 "이중 소비" 하도록 허용 하지 않습니다. - -## 쓰기 권한 - -누군가 TRANSFER 트랜잭션을 만들면, `metadata` 필드에 임의의 JSON 객체를 넣을 수 있다. (적정 범위 내에서; 실제 Planetmint 네트워크는 트랜잭션의 크기에 제한을 둔다.) 즉, TRANSFER 트랜잭션에서 원하는 모든 것을 쓸 수 있다. - -Planetmint에서 "쓰기 권한"이 없다는 의미인가요? 아닙니다!! - -TRANSFER 트랜잭션은 입력이 이전 출력을 충족시키는 경우에만 유효(허용)합니다. 이 출력들에 대한 조건은 누가 유효한 TRANSFER 트랜잭션을 할 수 있는지 조절 할 것입니다. 즉, 출력에 대한 조건은 특정 사용자에게 관련 자산 내역에 무엇인가 쓸 수 있는 "쓰기 권한"을 부여하는 것과 같습니다. - -예를 들어, 당신은 Planetmint를 사용하여 오직 당신만이 쓰기권한이 있는 공용 저널을 작성 할 수 있습니다. 방법은 다음과 같습니다: 먼저 하나의 출력으로 `asset.data` 을 통해 `{"title": "The Journal of John Doe"}` 와 같이 되도록 CREATE 트랜잭션을 생성합니다. 이 출력에는 금액 1과 사용자(개인 키를 가진)만이 출력을 보낼 수 있는 조건이 있습니다. 저널에 무엇인가를 추가하고 싶을 때마다, `metadata` 같은 필드에 최신 항목을 넣은 TRANSFER 트랜잭션을 새로 만들어야 합니다. - -```json -{"timestamp": "1508319582", - "entry": "I visited Marmot Lake with Jane."} -``` - -TRANSFER 트랜잭션에는 하나의 출력이 있습니다. 이 출력에는 금액1과 사용자(개인키를 가진)만이 출력을 보낼 수 있는 조건이 있습니다. 기타 등등. 당신만이 자산 내역(당신의 저널)에 덧붙일 수 있습니다. - -이와 같은 기술은 공학 노트북,공급망 기록,정부 회의록 등에도 사용 될 수 있습니다. - -또한 더 정교한 것들도 할 수 있습니다. 예를 들어, 누군가가 TRANSFER 트랜잭션을 작성할 때마다, *다른 누군가*에게 사용 권한을 부여하여 일종의 작성자-전달 혹은 연쇄 편지를 설정한다. - -Note - -누구나 CREATE 트랜잭션의 `asset.data` 필드에 있는 JSON(조건하에)을 쓸 수 있습니다. 허가가 필요하지 않습니다. - -## 읽기 권한 - -다음 페이지를 참고하세요, [:doc:Planetmint, Privacy and Private Data](https://github.com/planetmint/planetmint/blob/master/docs/root/source/korean/private-data-ko.md). - -## 역할 기반 액세스 제어(RBAC) - -2017년 9월에, 우리는 [Planetmint RBAC 하부 시스템을 정의 할 수 있는 방법에 대한 블로그 게시물](https://blog.bigchaindb.com/role-based-access-control-for-planetmint-assets-b7cada491997)을 게재 했습니다. 글을 쓴 시점(2018년 1월)에는 플러그인을 사용해야 해서, 표준 Planetmint다음에서 사용가능한 [Planetmint Testnet](https://testnet.planetmint.io/) 를 사용 할 수 없었습니다. 이는 미래에 바뀔 수 있습니다. 만약 관심이 있다면, [Planetmint로 연락하십시요.](https://www.planetmint.io/contact/) diff --git a/docs/root/source/korean/private-data-ko.md b/docs/root/source/korean/private-data-ko.md deleted file mode 100644 index 1fb6dfd..0000000 --- a/docs/root/source/korean/private-data-ko.md +++ /dev/null @@ -1,102 +0,0 @@ - - -# Planetmint, 개인정보 및 개인 데이터 - -## 기본 정보 - -1. 한도 내에서 Planetmint 네트워크에 임의의 데이터(암호화 된 데이터 포함)를 저장 할 수 있습니다. 모든 트랜잭션에는 거의 모든 유니코드 문자열(최대 길이까지)을 저장 할 수 있는 `metadata` 섹션이 있습니다. 마찬가지로, 모든 CREATE 트랜잭션에는 거의 모든 유니코드 문자열을 저장 할 수 있는 `asset.data` 섹션이 있습니다. -2. 특정 Planetmint 거래 필드에 저장된 데이터는 암호화 해서는 안됩니다, 예를 들어 공용키 및 자산과 같이. Planetmint는 Zcoin과 비슷한 개인 거래를 제공하지 않습니다. -3. 데이터가 BigchinDB 네트워크에 저장되면 변경 또는 삭제 될 수 없다고 가정하는 것이 좋습니다. -4. Planetmint 네트워크의 모든 노드에는 저장된 모든 데이터의 전체 복사본이 있습니다. -5. Planetmint 네트워크의 모든 노드는 저장된 모든 데이터를 읽을 수 있습니다. -6. Planetmint 노드(예를 들어 노드이 sysadmin)에 대한 전체 액세스 권한을 가진 모든 사용자는해당 노드에 저장된 모든 데이터를 읽을 수 있습니다. -7. Planetmint HTTP API를 통해 노드에 접근하는 모든 사용자는 Planetmint에 저장된 모든 데이터를 찾고 읽을 수 있습니다. 액세스 권한이 있는 사람들의 목록은 매우 짧을 수 있습니다. -8. 외부 사용자와 Planetmint 노드 사이의 연결이(예를 들어 HTTPS를 사용하여) 암호화되 않으면도청자는 전송중인 모든 HTTP 요청 및 응답을 읽을 수 있습니다. -9. 만약 누군가가 평문에 접근 할 수 있다면(어디에서 가져왔는지 관계없이), 원칙적으로 이것을 전 세계와 공유 할 수 있습니다. 그렇게 하는 것을 어렵게 만들 수 있습니다, 예를 들어 데이터가 많고 방을 나갈 떄 검색되는 안전한 방 안에만 들어 갈 수 있는 것과 같습니다. - -## 오프 체인에서 개인 데이터 저장 - -시스템은 제3자 데이터베이스, 문서 저장소 또는 CMS(컨텐츠 관리 시스템)와 같은 오프 체인 데이터를 저장할 수 있으며, BigchinDB를 사용하여 다음 작업을 수행할 수 있습니다: - -- 제3자 시스템에 읽기 권한 또는 기타 권한이 있는 사용자를 추적합니다. 이 작업을 수행하는 방법의 예는 아래에 있습니다. -- 제3자 시스템에 대한 모든 요청을 영구적으로 기록합니다. -- 모든 문서의 변경 사항을 감지 할 수 있도록, 다른 곳에 저장된 문서의 해시를 저장합니다. -- 암호화 된 터널을 설정했다는 것을 증명할 수 있도록 두 개의 오프 체인 파티(예:Diffie-Hellman 키 교환) 간의 모든 핸드셰이크 설정 요청 및 응답을 기록합니다(독자가 해당 터널에 액세스하지 않고). 이 아이디어에 대한 자세한 내용은 [the Planetmint Privacy Protocols 저장소](https://github.com/planetmint/privacy-protocols)에 있습니다. - -특정 문서에 대한 읽기 권한을 가진 사람을 기록하는 간단한 방법은 제 3자 시스템(“Docpile“)이 모든 문서+사용자 쌍에 대해 BigchinDB 네트워크에 CREATE 트랜잭션을 저장하여 해당 사용자가 그 문서에 대한 읽기 권한을 가지고 있음을 나타낼 수 있습니다. 트랜잭션은 Docpile에 의해 서명 될 수 있습니다(또는 문서 소유자에 의해). 자산 데이터 필드는 1)사용자의 고유 ID 및 2)문서의 고유 ID를 포함합니다. CREATE 트랜잭션의 한 출력은 DocPile(또는 문서 소유자)에 의해서만 전송/소비 될 수 있습니다. - - -읽기 권한을 취소하기 위해, DocPile은 원래 사용자가 더 이상 해당 문서에 대한 읽기 권한을 가지고 있지 않다고 하는 메타 데이터 필드를 사용하여, 원래의 CREATE 트랜잭션에서 하나의 출력을 보내기 위한 TRANSFER 트랜잭션을 생성 할 수 있습니다. - -이는 무한정으로 수행될 수 있습니다,즉.사용자가 다시 읽기 권한을 가지고 있음을 나타내기 위해 다른 TRANSFER 트랜잭션을 DocPile에서 작성할 수 있습니다. - -DocPile은 CREATE → TRANSFER → TRANSFER → 사용자+문서 쌍에 대한 etc.chain 과정에서 사용자의 마지막 트랜잭션을 읽음으로써 주어진 문서에 대한 읽기 권한을 가지고 있는지 파악할 수 있습니다. - -여기에 같은 일을 하는 다른 방법들이 있다. 위는 단지 하나의 예시이다. - -위의 예시에서는 사용자가 소유한(통제 된)자산으로 “읽기 권한“을 취급하지 않았다는 것을 알 수 있습니다, 왜냐하면 사용 권한 자산이 사용자에게 주어 지면(사용자에 의해 양도되거나 사용자에 의해 생성된 경우) 사용자가 다시 Docpile로 전송 할 때까지 어떠한 것도 제어 할 수 없기 때문입니다(Docpile에 의해). - -## 체인에서 암호화 된 개인 데이터 저장 - -체인상에서 개인 데이터를 암호화하여 저장하는 방법에는 여러 가지가 있습니다. 모든 유스 케이스에는 고유한 목표와 제약이 있으며, 최상의 해결책은 유스 케이스에 달려있다. -[Planetmint 컨설팅 팀](https://www.planetmint.io/services/), 우리의 파트너와 함께, 당신의유스 케이스에 가장 적합한 솔루션을 설계하는 데 도움을 줄 수 있습니다. - -아래에서는 다양한 암호화 기본 설정을 사용하여 가능한 시스템을 설정하는 예제를 설명합니다. - -참고 사항: - -- Ed25519 키 쌍은 [메시지 암호화 및 암호 해독이 아닌](https://crypto.stackexchange.com/questions/27866/why-curve25519-for-encryption-but-ed25519-for-signatures) 암호화 서명 및 확인을 위해 설계되었습니다. 암호화의 경우, X25519와 같은 암호화를 위해 설계된 키 쌍을 사용해야 합니다. -- 누군가(또는 어떤 그룹)이 체인상의 암호화 된 데이터를 해독하는 방법을 발표하면 암호화 된 데이터에 액세스 할 수 있는 모든 사람이 평문을 가져올 수 있습니다. 데이터는 삭제할 수 없습니다. -- 암호화 된 데이터는 MongoDM에서 색인을 생성하거나 검색 할 수 없습니다.(암호문을 색인화하고 검색 할 수 있지만 유용하지는 않습니다.) 암호화 된 데이터를 색인화하고 검색하기 위해 준 유사 암호를 사용할 수 있지만, MongoDB는 이를 지원할 계획이 없습니다. 색인화 또는 키워드 검색이 필요한 경우 `asset.data`의 몇가지 필드 또는 `metadata`객체를 일반 텍스트로 남겨두고 민감한 정보를 암호화 된 하위 객체에 저장할 수 있습니다. - -### 시스템 예시 1 - -대칭 키로 데이터를 암호화하고 체인에(`metadata` 또는 `asset.data` 에서) 암호문을 저장하십시오. 키를 제 3자에게 알리려면, 공용 키를 사용하여 대칭 키를 암호화하고 암호화 키를 보냅니다. 개인 키로 대칭 키의 암호를 해독한 다음 대칭 키를 사용하여 on-chain 암호문의 암호를 해독할 수 있습니다. - -공용 키/ 개인 키 쌍과 함께 대칭 키를 사용하는 이유는 암호문을 한 번만 저장하면 되기 때문입니다. - -### 시스템 예시 2 - -이 예시에서는 [프록시 재-암호화](https://en.wikipedia.org/wiki/Proxy_re-encryption) 를 사용합니다: - -1. MegaCorp는 자체 공용 키를 사용하여 일부 데이터를 암호화 한 후 암호화 된 데이터(암호문1)을 Planetmint 네트워크에 저장합니다. - -2. MegaCorp는 다른 사람들이 암호화 된 데이터를 읽을 수 있게 하고 싶지만, 공용 키를 공유하지 않고 모든 새로운 수신자에 대해 스스로를 다시 암호화 할 필요가 없습니다. 대신 프록시 재 암호화 서비스를 제공하기 위해 Moxie라는 “프록시“를 찾습니다. -3. Zorban은 MegaCorp에 연결하여 데이터 읽기 권한을 요청합니다. -4. MegaCorp는 Zorban에게 공용 키를 요청합니다. -5. MegaCorp “재 암호화 키“를 생성하여 프록시 Moxie로 전송합니다. -6. Moxie (프록시)는 재 암호화 키를 사용하여 암호문 1을 암호화하고 암호문 2를 만듭니다. -7. Moxie는 Zorban(또는 Zorban에게 전달하는 MegaCorp)에게 암호문 2를 보냅니다. -8. Zorban은 개인 키를 사용하여 암호문 2를 해독해서 원본 암호화되지 않은 데이터를 가져옵니다. - -참고: - -- 프록시는 암호문만 볼 수 있습니다. 암호화 되지 않은 데이터는 볼 수 없습니다. -- Zorban은 암호문 1, 즉 체인 상의 데이터를 해독 할 수 있는 능력이 없습니다. -- 위의 흐름에는 다양한 변형이 있습니다. - -## 시스템 예시 3 - -이 예시는 [삭제 코딩](https://en.wikipedia.org/wiki/Erasure_code)을 사용합니다: - -1. 데이터를 n개의 조각으로 삭제하십시오. -2. 서로 다른 암호화 키로 n개의 조각을 암호화 하십시오. -3. n 개의 암호화 된 부분을 체인에 저장합니다 (예: n개의 별도 트랜잭션). -4. n 개의 암호 해독 키 각각을 다른 당사자와 공유하십시오. - -만약 k< N 인 키홀더가 k개의 조각들을 가져와서 해독한다면, 그것들은 원본 텍스트를 다시 만들 수 있습니다. k미만이면 충분하지 않습니다. - -### 시스템 예시 4 - -이 설정은 특수 노드가 데이터의 일부를 볼 수 있어야 하지만, 다른 노드는 볼 수 없어야 하는 기업용 블록 체인 시나리오에서 사용할 수 있습니다. - -- 특수 노드는 X25519 키 쌍 (또는 유사한 비대칭 *암호화*키 쌍)을 생성합니다 . -- Planetmint 최종 사용자는 특수 노드의 X25519 공용 키(암호화 키)를 찾습니다. - -최종 사용자는 위에서 언급 한 공용 키를 사용하여, asset.data 또는 메타 데이터(또는 모두)를 사용하여 유효한 Planetmint 트랜잭션을 생성합니다. -- 이는 asset.data 또는 메타 데이터의 내용이 유효성 검증에 중요하지 않은 트랜잭션에 대해서만 수행되므로, 모든 노드 운영자가 트랜잭션을 검증 할 수 있습니다. -- 특수 노드는 암호화 된 데이터를 해독 할 수 있지만, 다른 노드 운영자와 다른 최종 사용자는 할 수 없습니다. diff --git a/docs/root/source/korean/production-ready_kor.md b/docs/root/source/korean/production-ready_kor.md deleted file mode 100644 index e79fd01..0000000 --- a/docs/root/source/korean/production-ready_kor.md +++ /dev/null @@ -1,12 +0,0 @@ - - -# 배포 - 준비 - -경우에 따라, Planetmint는 배포-준비가 될 수도 있고 되지 않을 수도 있습니다. 서비스 공급자에게 문의해야 합니다. 만약 Planetmint를 (배포로) 전환하고자 한다면, 서비스 공급자에게 문의하십시오. - -참고 : Planetmint는 "보증 없음" 섹션을 가지는 오픈소스 라이센스이며, 이는 전형적인 오픈소스 라이센스입니다. 이는 소프트웨어 산업의 표준입니다. 예를 들어, 리눅스 커널은 라이센스에 "보증 없음" 섹션을 가지고 있지만, 수십억 대의 시스템에 의해 배포되어 사용됩니다. 보증은 대개 서비스 공급자가 소프트웨어 라이센스 수준 이상으로 제공합니다. diff --git a/docs/root/source/korean/query-ko.md b/docs/root/source/korean/query-ko.md deleted file mode 100644 index d3c66cc..0000000 --- a/docs/root/source/korean/query-ko.md +++ /dev/null @@ -1,202 +0,0 @@ - - -Planetmint 쿼리 -=================== - -노드 operator는 MongoDB의 쿼리 엔진의 최대 성능을 사용하여 모든 트랜잭션, 자산 및 메타데이터를 포함하여 저장된 모든 데이터를 검색하고 쿼리할 수 있습니다. 노드 operator는 외부 사용자에게 얼마나 많은 쿼리 파워를 송출할지 스스로 결정할 수 있습니다. - - -예제 쿼리가 포함된 블로그 게시물 ------------------------------- - - -Planetmint 블로그에 MongoDB 도구를 사용하여 Planetmint 노드의 MongoDB 데이터베이스를 쿼리하는 방법에 대한 게시물을 올렸습니다. 데이터에 대한 일부 특정 예제 쿼리가 주요 내용입니다. [여기서 확인하세요](https://blog.bigchaindb.com/using-mongodb-to-query-bigchaindb-data-3fc651e0861b) - -MongoDB에 연결하기 -------------------------- - - -MongoDB 데이터베이스를 쿼리하려면 먼저 데이터베이스에 연결해야 합니다. 그러기 위해선 호스트 이름과 포트를 알아야 합니다. - -개발 및 테스트를 위해 지역 컴퓨터에서 Planetmint 노드를 실행 중인 경우 호스트 이름은 "로컬 호스트"여야 하며 이러한 값을 변경하지 않는 한 포트는 "27017"이어야 합니다. 원격 시스템에서 Planetmint 노드를 실행 중이며 해당 시스템에 SSH할 수 있는 경우에도 마찬가지입니다. - -원격 시스템에서 Planetmint 노드를 실행하고 MongoDB를 auth를 사용하고 공개적으로 액세스할 수 있도록 구성한 경우(권한이 있는 사용자에게) 호스트 이름과 포트를 확인할 수 있습니다. - -쿼리하기 ------------- - -Planetmint 노드 운영자는 로컬 MongoDB 인스턴스에 대한 전체 액세스 권한을 가지므로 실행하는데 MongoDB의 다음의 API를 사용할 수 있습니다: - -- [the Mongo Shell](https://docs.mongodb.com/manual/mongo/) -- [MongoDB Compass](https://www.mongodb.com/products/compass) -- one of [the MongoDB drivers](https://docs.mongodb.com/ecosystem/drivers/), such as [PyMongo](https://api.mongodb.com/python/current/), or -- MongoDB 쿼리에 대한 서드파티툴, RazorSQL, Studio 3T, Mongo Management Studio, NoSQLBooster for MongoDB, or Dr. Mongo. - -Note - -SQL을 이용해 mongoDB 데이터베이스를 쿼리할 수 있습니다. 예를 들어: - - * Studio 3T: "[How to Query MongoDB with SQL](https://studio3t.com/whats-new/how-to-query-mongodb-with-sql/)" - * NoSQLBooster for MongoDB: "[How to Query MongoDB with SQL SELECT](https://mongobooster.com/blog/query-mongodb-with-sql/)" - -예를 들어, 기본 Planetmint 노드를 실행하는 시스템에 있는 경우 Mongo Shell (``mongo``)을 사용하여 연결하고 다음과 같이 볼 수 있습니다. - - $ mongo - MongoDB shell version v3.6.5 - connecting to: mongodb://127.0.0.1:27017 - MongoDB server version: 3.6.4 - ... - > show dbs - admin 0.000GB - planetmint 0.000GB - config 0.000GB - local 0.000GB - > use planetmint - switched to db planetmint - > show collections - abci_chains - assets - blocks - elections - metadata - pre_commit - transactions - utxos - validators - -위 예제는 몇 가지 상황을 보여줍니다: - -- 호스트 이름이나 포트를 지정하지 않으면 Mongo Shell은 각각 `localhost`와 `27017`으로 가정합니다. (`localhost`는 우분투에 IP주소를 127.0.0.1로 설정했습니다.) - - -* Planetmint는 데이터를 `planetmint`이라는 데이터베이스에 저장합니다. -* `planetmint` 데이터베이스에는 여러 [collections](https://docs.mongodb.com/manual/core/databases-and-collections/)가 포함되어 있습니다. -* 어떤 컬렉션에도 투표가 저장되지 않습니다. 이런 데이터는 모두 자체(LevelDB) 데이터베이스에 의해 처리되고 저장됩니다. - -컬렉션에 대한 예시 문서 ---------------------------------------- - -``planetmint`` 데이터베이스의 가장 흥미로운 부분은 아래와 같습니다: - -- transactions -- assets -- metadata -- blocks - -`db.assets.findOne()` 은 MongoDB 쿼리를 사용하여 이러한 컬렉션들을 탐색할 수 있습니다. - -### 트랜잭션에 대한 예시 문서 - -transaction 컬렉션에서 CREATE 트랜잭션에는 추가 `"_id"` 필드(MongoDB에 추가됨)가 포함되며 `"asset"`과 `"metadata"` 필드에는 데이터가 저장되어 있지 않습니다. - - { - "_id":ObjectId("5b17b9fa6ce88300067b6804"), - "inputs":[…], - "outputs":[…], - "operation":"CREATE", - "version":"2.0", - "id":"816c4dd7…851af1629" - } - -A TRANSFER transaction from the transactions collection is similar, but it keeps its `"asset"` field. - - { - "_id":ObjectId("5b17b9fa6ce88300067b6807"), - "inputs":[…], - "outputs":[…], - "operation":"TRANSFER", - "asset":{ - "id":"816c4dd7ae…51af1629" - }, - "version":"2.0", - "id":"985ee697d…a3296b9" - } - -### assets에 대한 예시 문서 - -assets에 대한 기술에는 MongoDB가 추가한 `"_id"` 분야와 CREATE 거래에서 나온 `asset.data` 그리고 `"id"` 세 가지 최상위 분야로 구성되어 있습니다. - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -{ - "_id":ObjectId("5b17b9fe6ce88300067b6823"), - "data":{ - "type":"cow", - "name":"Mildred" - }, - "id":"96002ef8740…45869959d8" -} - -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -### metadata에 대한 예시 문서 - - -metadata 컬렉션의 문서는 MongoDB가 추가한 `"_id"`필드와 거래에서 나온 `asset.data`그리고 거래에서 나온 ``"id"`` 세 가지 최상위 분야로 구성되어 있습니다. - - { - "_id":ObjectId("5b17ba006ce88300067b683d"), - "metadata":{ - "transfer_time":1058568256 - }, - "id":"53cba620e…ae9fdee0" - } - -### blocks에 대한 예시 문서 - - { - "_id":ObjectId("5b212c1ceaaa420006f41c57"), - "app_hash":"2b0b75c2c2…7fb2652ce26c6", - "height":17, - "transactions":[ - "5f1f2d6b…ed98c1e" - ] - } - -## 노드 operator가 외부 유저에게 보낼 수 있는 것 - -각 노드 operator는 외부 사용자가 자신의 로컬 MongoDB 데이터베이스에서 정보를 얻는 방법을 결정할 수 있습니다. 그들은 다음과 같은 것들을 보낼 수 있습니다: - -- 외부유저를 쿼리 처리하는 로컬 MongoDB 데이터베이스 한된 제한된 권한을 가진 역할을 가진 MongoDB 사용자 예) read-only -- 제한된 미리 정의된 쿼리 집합을 허용하는 제한된 HTTP API, [Planetmint 서버에서 제공하는 HTTP API](http://planetmint.io/http-api), 혹은Django, Express, Ruby on Rails, or ASP.NET.를 이용해 구현된 커스텀 HTTP API -- 다른 API(예: GraphQL API) 제3자의 사용자 정의 코드 또는 코드를 사용하여 수행할 수 있습니다.. - -각 노드 operator는 로컬 MongoDB 데이터베이스에 대한 다른 레벨 또는 유형의 액세스를 노출할 수 있습니다. -예를 들어, 한 노드 operator가 최적화된 [공간 쿼리](https://docs.mongodb.com/manual/reference/operator/query-geospatial/)를 전문으로 제공하기로 정할 수 있습니다. - -보안 고려사항 ------------------------ - -Planetmint 버전 1.3.0 이전 버전에서는 하나의 MongoDB 논리 데이터베이스가 있었기 때문에 외부 사용자에게 데이터베이스를 노출하는 것은 매우 위험했으며 권장되지 않습니다. "Drop database"는 공유된 MongoDB 데이터베이스를 삭제합니다. - -Planetmint 버전 2.0.0 이상에선 각 노드에 고유한 독립 로컬 MongoDB 데이터베이스가 존재합니다. 노드 간 통신은 아래 그림 1에서와 같이 MongoDB 프로토콜이 아닌 Tendermint 프로토콜을 사용하여 수행됩니다. 노드의 로컬 MongoDB 데이터베이스가 손상되어도 다른 노드는 영향을 받지 않습니다. - -![image](https://user-images.githubusercontent.com/36066656/48752907-f1dcd600-ecce-11e8-95f4-3cdeaa1dc4c6.png) - -Figure 1: A Four-Node Planetmint 2.0 Network - -퍼포먼스 및 요금 고려사항 ------------------------------------ - -쿼리 프로세싱은 상당히 많은 리소스를 소모할 수 있으므로, Planetmint 서버 및 Tendermint Core와 별도의 컴퓨터에서 MongoDB를 실행하는 것이 좋습니다. - -노드 operator 는 조회에 사용되는 리소스를 측정하여 조회를 요청한 사람은 누구든지 요금을 청구할 수 있습니다. - -일부 쿼리는 너무 오래 걸리거나 리소스를 너무 많이 사용할 수 있습니다. 노드 operator는 사용할 수 있는 리소스에 상한을 두고, 초과된다면 중지(또는 차단)해야 합니다. - -MongoDB 쿼리를 더욱 효율적으로 만들기 위해 [인덱스](https://docs.mongodb.com/manual/indexes/)를 만들 수 있습니다. 이러한 인덱스는 노드 operator 또는 일부 외부 사용자가 생성할 수 있습니다(노드 운영자가 허용하는 경우). 인덱스는 비어 있지 않습니다. 새 데이터를 컬렉션에 추가할 때마다 해당 인덱스를 업데이트해야 합니다. 노드 운영자는 이러한 요금을 인덱스를 생성한 사람에게 전달하고자 할 수 있습니다. mongoDB에서는 [단일 컬렉션은 64개 이하의 인덱스를 가질 수 있습니다](https://docs.mongodb.com/manual/reference/limits/#Number-of-Indexes-per-Collection). - -Tendermint voting 파워가 0인 노드인 추종자 노드를 생성할 수 있다. 여전히 모든 데이터의 복사본이 있으므로 읽기 전용 노드로 사용할 수 있습니다. Follower 노드는 투표 검증자의 작업 부하에 영향을 미치지 않고 서비스로 전문화된 쿼리를 제공할 수 있습니다(쓰기도 가능). 팔로워의 팔로워들도 있을 수 있습니다. - -자바스크립트 쿼리 코드 예시 ------------------------------- - -[MongoDB node.js 드라이버](https://mongodb.github.io/node-mongodb-native/?jmp=docs)와 같은 MongoDB 드라이버를 사용하여 다음 중 하나를 사용하여 노드의 MongoDB 데이터베이스에 연결할 수 있습니다. 여기 자바스크립트 쿼리 코드에 대한 링크가 있습니다. - -- [The Planetmint JavaScript/Node.js driver source code](https://github.com/bigchaindb/js-bidchaindb-driver) -- [Example code by @manolodewiner](https://github.com/manolodewiner/query-mongodb-bigchaindb/blob/master/queryMongo.js) -- [More example code by @manolodewiner](https://github.com/bigchaindb/bigchaindb/issues/2315#issuecomment-392724279) \ No newline at end of file diff --git a/docs/root/source/korean/smart-contracts_ko.md b/docs/root/source/korean/smart-contracts_ko.md deleted file mode 100644 index 10d89fc..0000000 --- a/docs/root/source/korean/smart-contracts_ko.md +++ /dev/null @@ -1,17 +0,0 @@ - - -Planetmint 및 스마트계약 -============================== - -Planetmint에는 스마트 계약 (즉, 컴퓨터 프로그램)의 소스 코드를 저장할 수 있지만 Planetmint는 임의의 스마트 계약을 실행하지 않습니다. - -Planetmint는 대체 가능한 자산과 대체 할 수없는 자산 모두를 전송할 수있는 권한을 가진 사람을 시행하는 데 사용할 수 있습니다. 이중 지출을 막을 것입니다. 즉, ERC-20 (대체 가능한 토큰) 또는 ERC-721 (대체 할 수없는 토큰) 스마트 계약 대신 Planetmint 네트워크를 사용할 수 있습니다. - -자산 이전 권한은 쓰기 권한으로 해석 될 수 있으므로 로그, 저널 또는 감사 내역에 기록 할 수있는 사람을 제어하는데 사용할 수 있습니다. [Planetmint의 사용 권한](https://github.com/planetmint/planetmint/blob/master/docs/root/source/korean/permissions-ko.md)에 대한 자세한 내용은 페이지에서 확인하십시오. - -Planetmint 네트워크는 oracles 또는 체인 간 통신 프로토콜을 통해 다른 블록 체인 네트워크에 연결할 수 있습니다. 이는 Planetmint를 다른 블록 체인을 사용하여 임의의 스마트 계약을 실행하는 솔루션의 일부로 사용할 수 있음을 의미합니다. diff --git a/docs/root/source/korean/store-files_ko.md b/docs/root/source/korean/store-files_ko.md deleted file mode 100644 index 92e8f30..0000000 --- a/docs/root/source/korean/store-files_ko.md +++ /dev/null @@ -1,14 +0,0 @@ - - -# Planetmint에 파일을 저장하는 방법 - -Planetmint 네트워크에 파일을 저장할 수는 있지만 그렇게하지 않는 것이 좋습니다. 파일이 아닌 구조화 된 데이터를 저장, 인덱싱 및 쿼리하는 데 가장 적합합니다. - -분산 된 파일 저장소를 원하면 Storj, Sia, Swarm 또는 IPFS / Filecoin을 확인하십시오. 파일 URL, 해시 또는 기타 메타 데이터를 Planetmint 네트워크에 저장할 수 있습니다. - -Planetmint 네트워크에 파일을 저장해야하는 경우,이를 수행하는 한 가지 방법은 긴 Base64 문자열로 변환 한 다음 해당 문자열을 하나 이상의 Planetmint 트랜잭션 (CREATE 트랜잭션의 `asset.data`)에 저장하는 것입니다 , 또는 어떤 거래의 `메타데이터` 일 수도있다. diff --git a/docs/root/source/korean/terminology_kor.md b/docs/root/source/korean/terminology_kor.md deleted file mode 100644 index fcca059..0000000 --- a/docs/root/source/korean/terminology_kor.md +++ /dev/null @@ -1,26 +0,0 @@ - - -# 용어 - -Planetmint와 관련돈 몇 가지 전문화된 용어가 있습니다. 시작하기에 앞서, 최소한 다음과 같은 사항을 알아야합니다. - -## Planetmint 노드 - -**Planetmint 노드**는 [Planetmint 서버](https://docs.planetmint.io/projects/server/en/latest/introduction.html) 및 관련된 소프트웨어를 실행하는 시스템(또는 논리적인 시스템)입니다. 각각의 노드는 한 개인이나 조직에 의해 제어될 수 있습니다. - -## Planetmint 네트워크 - -Planetmint 노드들의 집합은 서로 연결하여 **Planetmint 네트워크**를 형성할 수 있습니다. 해당 네트워크에서 각각의 노드는 동일한 소프트웨어를 실행합니다. Planetmint 네트워크는 모니터링 같은 것들을 하기 위한 추가적인 시스템이 있을 수 있습니다. - -## Planetmint 컨소시엄 - -Planetmint 네트워크에 노드들을 실행하는 사람과 조직은 **Planetmint 컨소시엄**(즉, 다른 조직)에 속합니다. 컨소시엄은 결정을 하기 위해 일종의 거버넌스 구조를 가져야합니다. 만약 Planetmint 네트워크가 단 하나의 회사에 의해서 운영된다면, "컨소시엄"은 단지 그 회사일 뿐입니다. - -**Planetmint 네트워크와 컨소시엄의 차이는 무엇일까요?** - -Planetmint 네트워크는 단지 연결된 노드들의 집합입니다. 컨소시엄은 하나의 Planetmint 네트워크를 가지는 조직이며, 해당 네트워크에서 각각의 노드는 다른 운영자를 가집니다. diff --git a/docs/root/source/korean/transaction-concepts_ko.md b/docs/root/source/korean/transaction-concepts_ko.md deleted file mode 100644 index f2a124a..0000000 --- a/docs/root/source/korean/transaction-concepts_ko.md +++ /dev/null @@ -1,61 +0,0 @@ - - -# 트랜잭션 개념 - -*트랜잭션*은 물건 (예 : 자산)을 등록, 발행, 생성 또는 전송하는 데 사용됩니다. - -트랜잭션은 Planetmint가 저장하는 가장 기본적인 종류의 레코드입니다. CREATE 트랜잭션과 TRANSFER 트랜잭션의 두 종류가 있습니다. - - -## 트랜잭션 생성 - -CREATE 트랜잭션은 Planetmint에서 한 가지 (또는 자산)의 이력을 등록, 발행, 생성 또는 다른 방법으로 시작하는 데 사용될 수 있습니다. 예를 들어, 신원이나 창작물을 등록 할 수 있습니다. 이러한 것들을 종종 "자산"이라고 부르지만 literal 자산이 아닐 수도 있습니다. - -Planetmint는 Planetmint Server v0.8.0부터 나눌 수있는 자산을 지원합니다. 이는 "공유"의 초기 숫자로 자산을 생성 / 등록 할 수 있음을 의미합니다. 예를 들어, CREATE 트랜잭션은 50 개의 오크 나무로 된 트럭로드를 등록 할 수 있습니다. 분할 가능한 자산의 각 주식은 서로 공유 할 수 있어야합니다. 주식은 대체 가능해야합니다. - -CREATE 트랜잭션은 하나 이상의 출력을 가질 수 있습니다. 각 출력에는 관련 금액이 있습니다. 출력에 연결된 공유 수입니다. 예를 들어 자산이 50 개의 오크 나무로 구성되어있는 경우 한 출력에는 한 소유자 세트에 35 개의 오크 나무가 있고 다른 출력에는 다른 소유자 세트에는 15 개의 오크 나무가있을 수 있습니다. - -또한 각 출력에는 연관된 조건이 있습니다. 출력을 전송 / 소비하기 위해 충족되어야하는 조건 (TRANSFER 트랜잭션에 의해). Planetmint는 다양한 조건을 지원합니다. 자세한 내용은 관련 [Planetmint 트랜잭션 Spec](https://github.com/planetmint/BEPs/tree/master/tx-specs/)과 관련된 **트랜잭션 구성 요소 : 조건 섹션**을 참조하십시오. - -![Example Planetmint CREATE transaction](./_static/CREATE_example.png) - -위의 예제에서는 Planetmint CREATE 트랜잭션 다이어그램을 보여줍니다. Pam은 자산 3 주를 소유 / 통제하고 다른 주식은 없습니다 (다른 산출물이 없으므로). - -각 출력에는 해당 출력의 조건과 연관된 모든 공개 키 목록이 있습니다. 다시 말하면, 그 목록은 "소유자"의 목록으로 해석 될 수 있습니다.보다 정확한 단어는 이행자, 서명자, 컨트롤러 또는 이전 가능 요소 일 수 있습니다. 관련 [Planetmint Transactions Spec](https://github.com/planetmint/BEPs/tree/master/tx-specs/) **소유자에 관한 참고 사항** 섹션을 참조하십시오. - -CREATE 트랜잭션은 모든 소유자가 서명해야합니다. (만약 당신이 그 서명을 원한다면, 그것은 인코딩되었지만 하나의 입력의 "이행"에있다.) - -## 트랜잭션 이전 - -트랜잭션 이전은 다른 트랜잭션 (CREATE 트랜잭션 또는 다른 TRANSFER 트랜잭션)에서 하나 이상의 출력을 전송 / 소비 할 수 있습니다. 이러한 출력물은 모두 동일한 자산과 연결되어야합니다. TRANSFER 트랜잭션은 한 번에 하나의 자산의 공유 만 전송할 수 있습니다. - -트랜잭션 이전의 각 입력은 다른 트랜잭션의 한 출력에 연결됩니다. 각 입력은 전송 / 소비하려는 출력의 조건을 충족해야합니다. - -트랜잭션 이전은 위에서 설명한 CREATE 트랜잭션과 마찬가지로 하나 이상의 출력을 가질 수 있습니다. 투입물에 들어오는 총 주식 수는 산출물에서 나가는 총 주식 수와 같아야합니다. - -![Example Planetmint transactions](./_static/CREATE_and_TRANSFER_example.png) - -위 그림은 두 개의 Planetmint 트랜잭션, CREATE 트랜잭션 및 TRANSFER 트랜잭션의 다이어그램을 보여줍니다. CREATE 트랜잭션은 이전 다이어그램과 동일합니다. TRANSFER 트랜잭션은 Pam의 출력을 소비하므로 TRANSFER 트랜잭션의 입력에는 Pam의 유효한 서명 (즉, 유효한 이행)이 포함되어야합니다. TRANSFER 트랜잭션에는 두 개의 출력이 있습니다. Jim은 하나의 공유를 가져오고 Pam은 나머지 두 개의 공유를 가져옵니다. - -용어 : "Pam, 3"출력을 "소비 된 트랜잭션 출력"이라고하며 "Jim, 1"및 "Pam, 2"출력을 "사용되지 않은 트랜잭션 출력"(UTXO)이라고합니다. - -**예제 1:** 빨간 차가 Joe가 소유하고 관리한다고 가정합니다. 자동차의 현재 전송 조건에서 Joe가 유효한 전송을 서명해야한다고 가정합니다. Joe는 Joe의 서명 (현재 출력 조건을 충족시키기 위해)과 Rae가 유효한 전송을 서명해야한다는 새로운 출력 조건을 포함하는 입력을 포함하는 TRANSFER 트랜잭션을 작성할 수 있습니다. - -**예제 2:** 예를 들어 동일한 자산 유형의 이전에 전송되지 않은 4 개의 자산에 대한 출력 조건을 충족하는 TRANSFER 트랜잭션을 생성 할 수 있습니다. 종이 클립. 총 금액은 20, 10, 45 및 25 일 수 있으며, 말하자면 총 100 개의 클립입니다. 또한 TRANSFER 트랜잭션은 새로운 전송 조건을 설정합니다. 예를 들어, Gertrude가 서명하는 경우에만 60 개의 클립 클립이 전송 될 수 있으며 Jack과 Kelly가 서명하는 경우에만 40 개의 클립 클립이 전송 될 수 있습니다. 들어오는 클립 클립의 합계가 나가는 클립 클립의 합계와 같아야합니다 (100). - -## 트랜잭션 유효성 - -언제 트랜잭션이 유효한지 유효성을 검사하는 것에 관해 해당 블로그에 게시되어있습니다. *The Planetmint Blog*: -["What is a Valid Transaction in Planetmint?"](https://blog.bigchaindb.io/what-is-a-valid-transaction-in-bigchaindb-9a1a075a9598) (Note: That post was about Planetmint Server v1.0.0.) - -Each [Planetmint Transactions Spec](https://github.com/planetmint/BEPs/tree/master/tx-specs/) documents the conditions for a transaction (of that version) to be valid. - -## 트랜잭션 예시 - -아래의 [HTTP API 문서](https://docs.planetmint.io/projects/server/en/latest/http-client-server-api.html)와 [the Python 드라이버 문서](https://docs.planetmint.io/projects/py-driver/en/latest/usage.html)에는 예제 Planetmint 트랜잭션이 있습니다. -. diff --git a/docs/root/source/installation/network-setup/index.rst b/docs/root/source/network-setup/index.rst similarity index 68% rename from docs/root/source/installation/network-setup/index.rst rename to docs/root/source/network-setup/index.rst index e21f5f9..1708f20 100644 --- a/docs/root/source/installation/network-setup/index.rst +++ b/docs/root/source/network-setup/index.rst @@ -4,16 +4,15 @@ SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) Code is Apache-2.0 and docs are CC-BY-4.0 -Network setup -============= +Networks & Federations +###################### + There are several ways to setup a network. You can use the Kubernetes deployment template in this section, or use the Ansible solution in the Contributing section. Also, you can setup a single node on your machine and connect to an existing network. -.. toctree:: - :maxdepth: 1 - - networks - network-setup - k8s-deployment-template/index - planetmint-node-ansible.md +.. include:: networks.md + :parser: myst_parser.sphinx_ +.. include:: network-setup.md + :parser: myst_parser.sphinx_ +.. include:: k8s-deployment-template/index.rst \ No newline at end of file diff --git a/docs/root/source/installation/network-setup/k8s-deployment-template/architecture.rst b/docs/root/source/network-setup/k8s-deployment-template/architecture.rst similarity index 100% rename from docs/root/source/installation/network-setup/k8s-deployment-template/architecture.rst rename to docs/root/source/network-setup/k8s-deployment-template/architecture.rst diff --git a/docs/root/source/installation/network-setup/k8s-deployment-template/ca-installation.rst b/docs/root/source/network-setup/k8s-deployment-template/ca-installation.rst similarity index 100% rename from docs/root/source/installation/network-setup/k8s-deployment-template/ca-installation.rst rename to docs/root/source/network-setup/k8s-deployment-template/ca-installation.rst diff --git a/docs/root/source/installation/network-setup/k8s-deployment-template/client-tls-certificate.rst b/docs/root/source/network-setup/k8s-deployment-template/client-tls-certificate.rst similarity index 100% rename from docs/root/source/installation/network-setup/k8s-deployment-template/client-tls-certificate.rst rename to docs/root/source/network-setup/k8s-deployment-template/client-tls-certificate.rst diff --git a/docs/root/source/installation/network-setup/k8s-deployment-template/cloud-manager.rst b/docs/root/source/network-setup/k8s-deployment-template/cloud-manager.rst similarity index 100% rename from docs/root/source/installation/network-setup/k8s-deployment-template/cloud-manager.rst rename to docs/root/source/network-setup/k8s-deployment-template/cloud-manager.rst diff --git a/docs/root/source/installation/network-setup/k8s-deployment-template/easy-rsa.rst b/docs/root/source/network-setup/k8s-deployment-template/easy-rsa.rst similarity index 100% rename from docs/root/source/installation/network-setup/k8s-deployment-template/easy-rsa.rst rename to docs/root/source/network-setup/k8s-deployment-template/easy-rsa.rst diff --git a/docs/root/source/installation/network-setup/k8s-deployment-template/index.rst b/docs/root/source/network-setup/k8s-deployment-template/index.rst similarity index 100% rename from docs/root/source/installation/network-setup/k8s-deployment-template/index.rst rename to docs/root/source/network-setup/k8s-deployment-template/index.rst diff --git a/docs/root/source/installation/network-setup/k8s-deployment-template/log-analytics.rst b/docs/root/source/network-setup/k8s-deployment-template/log-analytics.rst similarity index 100% rename from docs/root/source/installation/network-setup/k8s-deployment-template/log-analytics.rst rename to docs/root/source/network-setup/k8s-deployment-template/log-analytics.rst diff --git a/docs/root/source/installation/network-setup/k8s-deployment-template/node-config-map-and-secrets.rst b/docs/root/source/network-setup/k8s-deployment-template/node-config-map-and-secrets.rst similarity index 100% rename from docs/root/source/installation/network-setup/k8s-deployment-template/node-config-map-and-secrets.rst rename to docs/root/source/network-setup/k8s-deployment-template/node-config-map-and-secrets.rst diff --git a/docs/root/source/installation/network-setup/k8s-deployment-template/node-on-kubernetes.rst b/docs/root/source/network-setup/k8s-deployment-template/node-on-kubernetes.rst similarity index 100% rename from docs/root/source/installation/network-setup/k8s-deployment-template/node-on-kubernetes.rst rename to docs/root/source/network-setup/k8s-deployment-template/node-on-kubernetes.rst diff --git a/docs/root/source/installation/network-setup/k8s-deployment-template/planetmint-network-on-kubernetes.rst b/docs/root/source/network-setup/k8s-deployment-template/planetmint-network-on-kubernetes.rst similarity index 100% rename from docs/root/source/installation/network-setup/k8s-deployment-template/planetmint-network-on-kubernetes.rst rename to docs/root/source/network-setup/k8s-deployment-template/planetmint-network-on-kubernetes.rst diff --git a/docs/root/source/installation/network-setup/k8s-deployment-template/revoke-tls-certificate.rst b/docs/root/source/network-setup/k8s-deployment-template/revoke-tls-certificate.rst similarity index 100% rename from docs/root/source/installation/network-setup/k8s-deployment-template/revoke-tls-certificate.rst rename to docs/root/source/network-setup/k8s-deployment-template/revoke-tls-certificate.rst diff --git a/docs/root/source/installation/network-setup/k8s-deployment-template/server-tls-certificate.rst b/docs/root/source/network-setup/k8s-deployment-template/server-tls-certificate.rst similarity index 100% rename from docs/root/source/installation/network-setup/k8s-deployment-template/server-tls-certificate.rst rename to docs/root/source/network-setup/k8s-deployment-template/server-tls-certificate.rst diff --git a/docs/root/source/installation/network-setup/k8s-deployment-template/tectonic-azure.rst b/docs/root/source/network-setup/k8s-deployment-template/tectonic-azure.rst similarity index 100% rename from docs/root/source/installation/network-setup/k8s-deployment-template/tectonic-azure.rst rename to docs/root/source/network-setup/k8s-deployment-template/tectonic-azure.rst diff --git a/docs/root/source/installation/network-setup/k8s-deployment-template/template-kubernetes-azure.rst b/docs/root/source/network-setup/k8s-deployment-template/template-kubernetes-azure.rst similarity index 100% rename from docs/root/source/installation/network-setup/k8s-deployment-template/template-kubernetes-azure.rst rename to docs/root/source/network-setup/k8s-deployment-template/template-kubernetes-azure.rst diff --git a/docs/root/source/installation/network-setup/k8s-deployment-template/troubleshoot.rst b/docs/root/source/network-setup/k8s-deployment-template/troubleshoot.rst similarity index 100% rename from docs/root/source/installation/network-setup/k8s-deployment-template/troubleshoot.rst rename to docs/root/source/network-setup/k8s-deployment-template/troubleshoot.rst diff --git a/docs/root/source/installation/network-setup/k8s-deployment-template/upgrade-on-kubernetes.rst b/docs/root/source/network-setup/k8s-deployment-template/upgrade-on-kubernetes.rst similarity index 100% rename from docs/root/source/installation/network-setup/k8s-deployment-template/upgrade-on-kubernetes.rst rename to docs/root/source/network-setup/k8s-deployment-template/upgrade-on-kubernetes.rst diff --git a/docs/root/source/installation/network-setup/k8s-deployment-template/workflow.rst b/docs/root/source/network-setup/k8s-deployment-template/workflow.rst similarity index 100% rename from docs/root/source/installation/network-setup/k8s-deployment-template/workflow.rst rename to docs/root/source/network-setup/k8s-deployment-template/workflow.rst diff --git a/docs/root/source/installation/network-setup/network-setup.md b/docs/root/source/network-setup/network-setup.md similarity index 97% rename from docs/root/source/installation/network-setup/network-setup.md rename to docs/root/source/network-setup/network-setup.md index 47e84ea..8ccebe0 100644 --- a/docs/root/source/installation/network-setup/network-setup.md +++ b/docs/root/source/network-setup/network-setup.md @@ -198,7 +198,7 @@ If you want to start and manage the Planetmint and Tendermint processes yourself ## How Others Can Access Your Node -If you followed the above instructions, then your node should be publicly-accessible with Planetmint Root URL `https://hostname` or `http://hostname:9984`. That is, anyone can interact with your node using the [Planetmint HTTP API](../api/http-client-server-api) exposed at that address. The most common way to do that is to use one of the [Planetmint Drivers](../../drivers/index). +If you followed the above instructions, then your node should be publicly-accessible with Planetmint Root URL `https://hostname` or `http://hostname:9984`. That is, anyone can interact with your node using the [Planetmint HTTP API](../connecting/http-client-server-api) exposed at that address. The most common way to do that is to use one of the [Planetmint Drivers](../connecting/drivers). [bdb:software]: https://github.com/planetmint/planetmint/ [bdb:pypi]: https://pypi.org/project/Planetmint/#history diff --git a/docs/root/source/installation/network-setup/networks.md b/docs/root/source/network-setup/networks.md similarity index 94% rename from docs/root/source/installation/network-setup/networks.md rename to docs/root/source/network-setup/networks.md index fbe3d8a..6007306 100644 --- a/docs/root/source/installation/network-setup/networks.md +++ b/docs/root/source/network-setup/networks.md @@ -25,7 +25,7 @@ We now describe how *we* set up the external (public-facing) DNS records for a P There were several goals: * Allow external users/clients to connect directly to any Planetmint node in the network (over the internet), if they want. -* Each Planetmint node operator should get an SSL certificate for their Planetmint node, so that their Planetmint node can serve the [Planetmint HTTP API](../api/http-client-server-api) via HTTPS. (The same certificate might also be used to serve the [WebSocket API](../api/websocket-event-stream-api).) +* Each Planetmint node operator should get an SSL certificate for their Planetmint node, so that their Planetmint node can serve the [Planetmint HTTP API](../connecting/http-client-server-api) via HTTPS. (The same certificate might also be used to serve the [WebSocket API](../connecting/websocket-event-stream-api).) * There should be no sharing of SSL certificates among Planetmint node operators. * Optional: Allow clients to connect to a "random" Planetmint node in the network at one particular domain (or subdomain). diff --git a/docs/root/source/installation/node-setup/all-in-one-planetmint.md b/docs/root/source/node-setup/all-in-one-planetmint.md similarity index 97% rename from docs/root/source/installation/node-setup/all-in-one-planetmint.md rename to docs/root/source/node-setup/all-in-one-planetmint.md index f25f6cf..4727b85 100644 --- a/docs/root/source/installation/node-setup/all-in-one-planetmint.md +++ b/docs/root/source/node-setup/all-in-one-planetmint.md @@ -66,7 +66,7 @@ Let's analyze that command: $ docker ps | grep planetmint ``` -Send your first transaction using [Planetmint drivers](../../drivers/index). +Send your first transaction using [Planetmint drivers](../connecting/drivers). ## Building Your Own Image diff --git a/docs/root/source/installation/node-setup/aws-setup.md b/docs/root/source/node-setup/aws-setup.md similarity index 100% rename from docs/root/source/installation/node-setup/aws-setup.md rename to docs/root/source/node-setup/aws-setup.md diff --git a/docs/root/source/installation/node-setup/configuration.md b/docs/root/source/node-setup/configuration.md similarity index 98% rename from docs/root/source/installation/node-setup/configuration.md rename to docs/root/source/node-setup/configuration.md index 5707b65..3f2f293 100644 --- a/docs/root/source/installation/node-setup/configuration.md +++ b/docs/root/source/node-setup/configuration.md @@ -65,7 +65,7 @@ To use username/password authentication, a Tarantool instance must already be ru ## server.* `server.bind`, `server.loglevel` and `server.workers` -are settings for the [Gunicorn HTTP server](http://gunicorn.org/), which is used to serve the [HTTP client-server API](../api/http-client-server-api). +are settings for the [Gunicorn HTTP server](http://gunicorn.org/), which is used to serve the [HTTP client-server API](../connecting/http-client-server-api). `server.bind` is where to bind the Gunicorn HTTP server socket. It's a string. It can be any valid value for [Gunicorn's bind setting](http://docs.gunicorn.org/en/stable/settings.html#bind). For example: @@ -122,7 +122,7 @@ export PLANETMINT_SERVER_WORKERS=5 These settings are for the [aiohttp server](https://aiohttp.readthedocs.io/en/stable/index.html), which is used to serve the -[WebSocket Event Stream API](../api/websocket-event-stream-api). +[WebSocket Event Stream API](../connecting/websocket-event-stream-api). `wsserver.scheme` should be either `"ws"` or `"wss"` (but setting it to `"wss"` does *not* enable SSL/TLS). `wsserver.host` is where to bind the aiohttp server socket and diff --git a/docs/root/source/installation/node-setup/deploy-a-machine.md b/docs/root/source/node-setup/deploy-a-machine.md similarity index 96% rename from docs/root/source/installation/node-setup/deploy-a-machine.md rename to docs/root/source/node-setup/deploy-a-machine.md index dc84990..2b0c149 100644 --- a/docs/root/source/installation/node-setup/deploy-a-machine.md +++ b/docs/root/source/node-setup/deploy-a-machine.md @@ -25,7 +25,7 @@ using private IP addresses, but we don't cover that here.) ## Operating System -**Use Ubuntu 18.04 or Ubuntu Server 18.04 as the operating system.** +**Use Ubuntu 18.04 Server or above versions as the operating system.** Similar instructions will work on other versions of Ubuntu, and other recent Debian-like Linux distros, diff --git a/docs/root/source/node-setup/index.rst b/docs/root/source/node-setup/index.rst new file mode 100644 index 0000000..a25273b --- /dev/null +++ b/docs/root/source/node-setup/index.rst @@ -0,0 +1,31 @@ + +.. 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 + +Node setup +========== + +You can use the all-in-one docker solution, or install Tendermint, MongoDB, and Planetmint step by step. For more advanced users and for development, the second option is recommended. + + + +.. include:: deploy-a-machine.md + :parser: myst_parser.sphinx_ +.. include:: aws-setup.md + :parser: myst_parser.sphinx_ +.. include:: all-in-one-planetmint.md + :parser: myst_parser.sphinx_ +.. include:: planetmint-node-ansible.md + :parser: myst_parser.sphinx_ +.. include:: set-up-node-software.md + :parser: myst_parser.sphinx_ +.. include:: set-up-nginx.md + :parser: myst_parser.sphinx_ +.. include:: configuration.md + :parser: myst_parser.sphinx_ +.. include:: production-node/index.rst + :parser: myst_parser.sphinx_ + + diff --git a/docs/root/source/installation/node-setup/planetmint-node-ansible.md b/docs/root/source/node-setup/planetmint-node-ansible.md similarity index 100% rename from docs/root/source/installation/node-setup/planetmint-node-ansible.md rename to docs/root/source/node-setup/planetmint-node-ansible.md diff --git a/docs/root/source/node-setup/production-node/index.rst b/docs/root/source/node-setup/production-node/index.rst new file mode 100644 index 0000000..724c9eb --- /dev/null +++ b/docs/root/source/node-setup/production-node/index.rst @@ -0,0 +1,20 @@ + +.. 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 + +Production Nodes +================ + +.. include:: node-requirements.md + :parser: myst_parser.sphinx_ +.. include:: node-assumptions.md + :parser: myst_parser.sphinx_ +.. include:: node-components.md + :parser: myst_parser.sphinx_ +.. include:: node-security-and-privacy.md + :parser: myst_parser.sphinx_ +.. include:: reverse-proxy-notes.md + :parser: myst_parser.sphinx_ + diff --git a/docs/root/source/installation/node-setup/production-node/node-assumptions.md b/docs/root/source/node-setup/production-node/node-assumptions.md similarity index 100% rename from docs/root/source/installation/node-setup/production-node/node-assumptions.md rename to docs/root/source/node-setup/production-node/node-assumptions.md diff --git a/docs/root/source/installation/node-setup/production-node/node-components.md b/docs/root/source/node-setup/production-node/node-components.md similarity index 100% rename from docs/root/source/installation/node-setup/production-node/node-components.md rename to docs/root/source/node-setup/production-node/node-components.md diff --git a/docs/root/source/installation/node-setup/production-node/node-requirements.md b/docs/root/source/node-setup/production-node/node-requirements.md similarity index 100% rename from docs/root/source/installation/node-setup/production-node/node-requirements.md rename to docs/root/source/node-setup/production-node/node-requirements.md diff --git a/docs/root/source/installation/node-setup/production-node/node-security-and-privacy.md b/docs/root/source/node-setup/production-node/node-security-and-privacy.md similarity index 100% rename from docs/root/source/installation/node-setup/production-node/node-security-and-privacy.md rename to docs/root/source/node-setup/production-node/node-security-and-privacy.md diff --git a/docs/root/source/installation/node-setup/production-node/reverse-proxy-notes.md b/docs/root/source/node-setup/production-node/reverse-proxy-notes.md similarity index 100% rename from docs/root/source/installation/node-setup/production-node/reverse-proxy-notes.md rename to docs/root/source/node-setup/production-node/reverse-proxy-notes.md diff --git a/docs/root/source/installation/node-setup/set-up-nginx.md b/docs/root/source/node-setup/set-up-nginx.md similarity index 100% rename from docs/root/source/installation/node-setup/set-up-nginx.md rename to docs/root/source/node-setup/set-up-nginx.md diff --git a/docs/root/source/installation/node-setup/set-up-node-software.md b/docs/root/source/node-setup/set-up-node-software.md similarity index 100% rename from docs/root/source/installation/node-setup/set-up-node-software.md rename to docs/root/source/node-setup/set-up-node-software.md diff --git a/docs/root/source/terminology.md b/docs/root/source/terminology.md index 6827de7..fb4a5d0 100644 --- a/docs/root/source/terminology.md +++ b/docs/root/source/terminology.md @@ -11,8 +11,6 @@ There is some specialized terminology associated with Planetmint. To get started ## Planetmint Node -**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 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 @@ -27,6 +25,10 @@ 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. +## Validators + +A validator node is a Planetmint node that is a validator as it is defined for Tendermint (see [Tendermint Validator](https://docs.tendermint.com/master/nodes/validators.html)). + ## Transactions Are described in detail in `Planetmint Transactions Spec `_ . diff --git a/docs/root/source/tools/index.rst b/docs/root/source/tools/index.rst new file mode 100644 index 0000000..ebebdc3 --- /dev/null +++ b/docs/root/source/tools/index.rst @@ -0,0 +1,8 @@ +Tools +===== + +You can use the all-in-one docker solution, or install Tendermint, MongoDB, and Planetmint step by step. For more advanced users and for development, the second option is recommended. + + +.. include:: planetmint-cli.md + :parser: myst_parser.sphinx_ diff --git a/docs/root/source/installation/node-setup/planetmint-cli.md b/docs/root/source/tools/planetmint-cli.md similarity index 96% rename from docs/root/source/installation/node-setup/planetmint-cli.md rename to docs/root/source/tools/planetmint-cli.md index a3bab36..a29e855 100644 --- a/docs/root/source/installation/node-setup/planetmint-cli.md +++ b/docs/root/source/tools/planetmint-cli.md @@ -22,7 +22,7 @@ Show the version number. `planetmint -v` does the same thing. ## planetmint configure -Generate a local configuration file (which can be used to set some or all [Planetmint node configuration settings](configuration)). It will ask you for the values of some configuration settings. +Generate a local configuration file (which can be used to set some or all [Planetmint node configuration settings](../node-setup/configuration)). It will ask you for the values of some configuration settings. If you press Enter for a value, it will use the default value. At this point, only one database backend is supported: `tarantool`. @@ -42,7 +42,7 @@ planetmint -y configure tarantool ## planetmint show-config -Show the values of the [Planetmint node configuration settings](configuration). +Show the values of the [Planetmint node configuration settings](../node-setup/configuration). ## planetmint init @@ -79,7 +79,7 @@ section of Python's documentation. For a more fine-grained control over the logging configuration you can use the configuration file as documented under -[Configuration Settings](configuration). +[Configuration Settings](../node-setup/configuration). ## planetmint election @@ -94,7 +94,7 @@ Create a new election which proposes a change to the Planetmint network. If the command succeeds, it will post an election transaction and output `election_id`. -The election proposal consists of vote tokens allocated to every current validator proportional to his voting power. Validators spend their votes to approve the election using the [election-approve command](#election-approve). +The election proposal consists of vote tokens allocated to every current validator proportional to his voting power. Validators spend their votes to approve the election using the [election-approve command](election-approve). Every election has a type. Currently supported types are `upsert-validator` and `chain-migration`. Their transaction operations are `VALIDATOR_ELECTION` and `CHAIN_MIGRATION` accordingly. See below for how to create an election of a particular type. @@ -153,6 +153,7 @@ Afterwards, validators are supposed to upgrade Tendermint, set new `chain_id`, ` For more details about how chain migrations work, refer to [Type 3 scenarios in BEP-42](https://github.com/planetmint/BEPs/tree/master/42). +(election-approve)= ### election approve Approve an election by voting for it. The command places a `VOTE` transaction, spending all of the validator's vote tokens to the election address. @@ -173,6 +174,7 @@ $ planetmint election approve 04a067582cf03eba2b53b82e4adb5ece424474cbd4f7183780 Once a proposal has been approved by the sufficient amount of validators (contributing more than `2/3` of the total voting power), the proposed change is applied to the network. +(election-show)= ### election show Retrieves the information about elections. diff --git a/docs/root/source/installation/node-setup/troubleshooting.md b/docs/root/source/troubleshooting.md similarity index 96% rename from docs/root/source/installation/node-setup/troubleshooting.md rename to docs/root/source/troubleshooting.md index f72bca8..faa5b35 100644 --- a/docs/root/source/installation/node-setup/troubleshooting.md +++ b/docs/root/source/troubleshooting.md @@ -84,7 +84,7 @@ If you started Planetmint in the foreground, a `Ctrl + C` or `Ctrl + Z` would sh ## Member: Dynamically Add or Remove Validators -One member can make a proposal to call an election to add a validator, remove a validator, or change the voting power of a validator. They then share the election/proposal ID with all the other members. Once more than 2/3 of the voting power votes yes, the proposed change comes into effect. The commands to create a new election/proposal, to approve an election/proposal, and to get the current status of an election/proposal can be found in the documentation about the [planetmint election](../server-reference/planetmint-cli#planetmint-election) subcommands. +One member can make a proposal to call an election to add a validator, remove a validator, or change the voting power of a validator. They then share the election/proposal ID with all the other members. Once more than 2/3 of the voting power votes yes, the proposed change comes into effect. The commands to create a new election/proposal, to approve an election/proposal, and to get the current status of an election/proposal can be found in the documentation about the [planetmint election](tools/planetmint-cli#planetmint-election) subcommands. ## Logging diff --git a/integration/python/Dockerfile b/integration/python/Dockerfile index 2498a58..ca824d5 100644 --- a/integration/python/Dockerfile +++ b/integration/python/Dockerfile @@ -13,10 +13,8 @@ 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 \ + planetmint-cryptoconditions>=0.9.9 \ + planetmint-driver>=9.2.0 \ blns - diff --git a/integration/python/src/conftest.py b/integration/python/src/conftest.py index 808914b..3a4912e 100644 --- a/integration/python/src/conftest.py +++ b/integration/python/src/conftest.py @@ -5,37 +5,36 @@ 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""" +CONDITION_SCRIPT = """ + Scenario 'ecdh': create the signature of an object + Given I have the 'keyring' + Given that I have a 'string dictionary' named 'houses' inside 'asset' + When I create the signature of 'houses' + Then print the 'signature'""" -# 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 +FULFILL_SCRIPT = """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' + Given I have a 'signature' named 'signature' inside 'metadata' + When I verify the 'houses' has a signature in 'signature' by 'Alice' Then print the string 'ok'""" +SK_TO_PK = """Scenario 'ecdh': Create the keypair + Given that I am known as '{}' + Given I have the 'keyring' + When I create the ecdh public key + When I create the bitcoin address + Then print my 'ecdh public key' + Then print my 'bitcoin address'""" + +GENERATE_KEYPAIR = """Scenario 'ecdh': Create the keypair + Given that I am known as 'Pippo' + When I create the ecdh key + When I create the bitcoin key + Then print data""" + +ZENROOM_DATA = {"also": "more data"} + HOUSE_ASSETS = { "data": { "houses": [ @@ -46,23 +45,12 @@ HOUSE_ASSETS = { { "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'""" +metadata = {"units": 300, "type": "KG"} @pytest.fixture diff --git a/integration/python/src/test_zenroom.py b/integration/python/src/test_zenroom.py index 8f749a6..cce592e 100644 --- a/integration/python/src/test_zenroom.py +++ b/integration/python/src/test_zenroom.py @@ -1,84 +1,123 @@ -# 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 +import base58 +from hashlib import sha3_256 +from cryptoconditions.types.zenroom import ZenroomSha256 +from planetmint_driver.crypto import generate_keypair +from .helper.hosts import Hosts +from zenroom import zencode_exec +import time -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'] +def test_zenroom_signing( + gen_key_zencode, + secret_key_to_private_key_zencode, + fulfill_script_zencode, + zenroom_data, + zenroom_house_assets, + condition_script_zencode, +): - 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)) + biolabs = generate_keypair() + version = "2.0" - # CRYPTO-CONDITIONS: instantiate an Ed25519 crypto-condition for buyer - zenSha = ZenroomSha256(script=fulfill_script_zencode, keys=zen_public_keys, data=zenroom_data) + alice = json.loads(zencode_exec(gen_key_zencode).output)["keyring"] + bob = json.loads(zencode_exec(gen_key_zencode).output)["keyring"] + + zen_public_keys = json.loads( + zencode_exec( + secret_key_to_private_key_zencode.format("Alice"), + keys=json.dumps({"keyring": alice}), + ).output + ) + zen_public_keys.update( + json.loads( + zencode_exec( + secret_key_to_private_key_zencode.format("Bob"), + keys=json.dumps({"keyring": bob}), + ).output + ) + ) + + zenroomscpt = ZenroomSha256( + script=fulfill_script_zencode, data=zenroom_data, keys=zen_public_keys + ) + print(f"zenroom is: {zenroomscpt.script}") # CRYPTO-CONDITIONS: generate the condition uri - condition_uri = zenSha.condition.serialize_uri() + condition_uri_zen = zenroomscpt.condition.serialize_uri() + print(f"\nzenroom condition URI: {condition_uri_zen}") # CRYPTO-CONDITIONS: construct an unsigned fulfillment dictionary - unsigned_fulfillment_dict = { - 'type': zenSha.TYPE_NAME, - 'script': fulfill_script_zencode, - 'keys': zen_public_keys, + unsigned_fulfillment_dict_zen = { + "type": zenroomscpt.TYPE_NAME, + "public_key": base58.b58encode(biolabs.public_key).decode(), } - output = { - 'amount': '1000', - 'condition': { - 'details': unsigned_fulfillment_dict, - 'uri': condition_uri, + "amount": "10", + "condition": { + "details": unsigned_fulfillment_dict_zen, + "uri": condition_uri_zen, }, - 'data': zenroom_data, - 'script': fulfill_script_zencode, - 'conf': '', - 'public_keys': (zen_public_keys['Alice']['ecdh_public_key'], ), + "public_keys": [ + biolabs.public_key, + ], } - input_ = { - 'fulfillment': None, - 'fulfills': None, - 'owners_before': (zen_public_keys['Alice']['ecdh_public_key'], ), + "fulfillment": None, + "fulfills": None, + "owners_before": [ + biolabs.public_key, + ], } - + metadata = {"result": {"output": ["ok"]}} token_creation_tx = { - 'operation': 'CREATE', - 'asset': zenroom_house_assets, - 'metadata': None, - 'outputs': (output,), - 'inputs': (input_,), - 'version': '2.0', - 'id': None, + "operation": "CREATE", + "asset": zenroom_house_assets, + "metadata": metadata, + "outputs": [ + output, + ], + "inputs": [ + input_, + ], + "version": version, + "id": None, } # JSON: serialize the transaction-without-id to a json formatted string message = json.dumps( token_creation_tx, sort_keys=True, - separators=(',', ':'), + separators=(",", ":"), ensure_ascii=False, ) - try: - assert(not zenSha.validate(message=message)) - except JSONDecodeError: - pass - except ValueError: - pass + # major workflow: + # we store the fulfill script in the transaction/message (zenroom-sha) + # the condition script is used to fulfill the transaction and create the signature + # + # the server should ick the fulfill script and recreate the zenroom-sha and verify the signature - message = zenSha.sign(message, condition_script_zencode, alice) - assert(zenSha.validate(message=message)) + message = zenroomscpt.sign(message, condition_script_zencode, alice) + assert zenroomscpt.validate(message=message) + + message = json.loads(message) + fulfillment_uri_zen = zenroomscpt.serialize_uri() + + message["inputs"][0]["fulfillment"] = fulfillment_uri_zen + tx = message + tx["id"] = None + json_str_tx = json.dumps(tx, sort_keys=True, skipkeys=False, separators=(",", ":")) + # SHA3: hash the serialized id-less transaction to generate the id + shared_creation_txid = sha3_256(json_str_tx.encode()).hexdigest() + message["id"] = shared_creation_txid + + hosts = Hosts("/shared/hostnames") + pm_alpha = hosts.get_connection() + + sent_transfer_tx = pm_alpha.transactions.send_commit(message) + time.sleep(1) + + # Assert that transaction is stored on both planetmint nodes + hosts.assert_transaction(shared_creation_txid) + print(f"\n\nstatus and result : + {sent_transfer_tx}") diff --git a/integration/scripts/all-in-one.bash b/integration/scripts/all-in-one.bash index e719587..f60a581 100755 --- a/integration/scripts/all-in-one.bash +++ b/integration/scripts/all-in-one.bash @@ -4,14 +4,11 @@ # 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 & +# Tarantool startup and configuration +tarantool /usr/src/app/scripts/init.lua # Start services monit -d 5 -I -B \ No newline at end of file diff --git a/integration/scripts/init.lua b/integration/scripts/init.lua new file mode 100644 index 0000000..87fba97 --- /dev/null +++ b/integration/scripts/init.lua @@ -0,0 +1,86 @@ +#!/usr/bin/env tarantool +box.cfg { + listen = 3303, + background = true, + log = '.planetmint-monit/logs/tarantool.log', + pid_file = '.planetmint-monit/monit_processes/tarantool.pid' +} + +box.schema.user.grant('guest','read,write,execute,create,drop','universe') + +function indexed_pattern_search(space_name, field_no, pattern) + if (box.space[space_name] == nil) then + print("Error: Failed to find the specified space") + return nil + end + local index_no = -1 + for i=0,box.schema.INDEX_MAX,1 do + if (box.space[space_name].index[i] == nil) then break end + if (box.space[space_name].index[i].type == "TREE" + and box.space[space_name].index[i].parts[1].fieldno == field_no + and (box.space[space_name].index[i].parts[1].type == "scalar" + or box.space[space_name].index[i].parts[1].type == "string")) then + index_no = i + break + end + end + if (index_no == -1) then + print("Error: Failed to find an appropriate index") + return nil + end + local index_search_key = "" + local index_search_key_length = 0 + local last_character = "" + local c = "" + local c2 = "" + for i=1,string.len(pattern),1 do + c = string.sub(pattern, i, i) + if (last_character ~= "%") then + if (c == '^' or c == "$" or c == "(" or c == ")" or c == "." + or c == "[" or c == "]" or c == "*" or c == "+" + or c == "-" or c == "?") then + break + end + if (c == "%") then + c2 = string.sub(pattern, i + 1, i + 1) + if (string.match(c2, "%p") == nil) then break end + index_search_key = index_search_key .. c2 + else + index_search_key = index_search_key .. c + end + end + last_character = c + end + index_search_key_length = string.len(index_search_key) + local result_set = {} + local number_of_tuples_in_result_set = 0 + local previous_tuple_field = "" + while true do + local number_of_tuples_since_last_yield = 0 + local is_time_for_a_yield = false + for _,tuple in box.space[space_name].index[index_no]: + pairs(index_search_key,{iterator = box.index.GE}) do + if (string.sub(tuple[field_no], 1, index_search_key_length) + > index_search_key) then + break + end + number_of_tuples_since_last_yield = number_of_tuples_since_last_yield + 1 + if (number_of_tuples_since_last_yield >= 10 + and tuple[field_no] ~= previous_tuple_field) then + index_search_key = tuple[field_no] + is_time_for_a_yield = true + break + end + previous_tuple_field = tuple[field_no] + if (string.match(tuple[field_no], pattern) ~= nil) then + number_of_tuples_in_result_set = number_of_tuples_in_result_set + 1 + result_set[number_of_tuples_in_result_set] = tuple + end + end + if (is_time_for_a_yield ~= true) then + break + end + require('fiber').yield() + end + return result_set +end \ No newline at end of file diff --git a/planetmint/__init__.py b/planetmint/__init__.py index 36c755a..5d8d7e0 100644 --- a/planetmint/__init__.py +++ b/planetmint/__init__.py @@ -6,8 +6,8 @@ from planetmint.transactions.common.transaction import Transaction # noqa from planetmint import models # noqa from planetmint.upsert_validator import ValidatorElection # noqa -from planetmint.transactions.types.elections.vote import Vote # noqa -from planetmint.migrations.chain_migration_election import ChainMigrationElection +from planetmint.transactions.types.elections.vote import Vote # noqa +from planetmint.transactions.types.elections.chain_migration_election import ChainMigrationElection from planetmint.lib import Planetmint from planetmint.core import App diff --git a/planetmint/backend/connection.py b/planetmint/backend/connection.py index 5cbca15..3087aed 100644 --- a/planetmint/backend/connection.py +++ b/planetmint/backend/connection.py @@ -43,13 +43,10 @@ def connect(host: str = None, port: int = None, login: str = None, password: str if backend == "tarantool_db": modulepath, _, class_name = BACKENDS[backend].rpartition('.') Class = getattr(import_module(modulepath), class_name) - print("LOGIN " + str(login)) - print("PASSWORD " + str(password)) return Class(host=host, port=port, user=login, password=password, kwargs=kwargs) elif backend == "localmongodb": modulepath, _, class_name = BACKENDS[backend].rpartition('.') Class = getattr(import_module(modulepath), class_name) - print(Config().get()) dbname = _kwargs_parser(key="name", kwargs=kwargs) or Config().get()['database']['name'] replicaset = _kwargs_parser(key="replicaset", kwargs=kwargs) or Config().get()['database']['replicaset'] ssl = _kwargs_parser(key="ssl", kwargs=kwargs) or Config().get()['database']['ssl'] diff --git a/planetmint/backend/tarantool/connection.py b/planetmint/backend/tarantool/connection.py index 06ff3bc..83de646 100644 --- a/planetmint/backend/tarantool/connection.py +++ b/planetmint/backend/tarantool/connection.py @@ -10,30 +10,45 @@ from planetmint.config import Config from planetmint.transactions.common.exceptions import ConfigurationError from planetmint.utils import Lazy from planetmint.backend.connection import Connection -from planetmint.utils import Lazy logger = logging.getLogger(__name__) class TarantoolDBConnection(Connection): - def __init__(self, host: str = "localhost", port: int = 3303, user: str = None, password: str = None, **kwargs): + def __init__( + self, + host: str = "localhost", + port: int = 3303, + user: str = None, + password: str = None, + **kwargs, + ): try: super().__init__(**kwargs) self.host = host self.port = port # TODO add user support later on - print(f"host : {host}") - print(f"port : {port}") self.init_path = Config().get()["database"]["init_config"]["absolute_path"] self.drop_path = Config().get()["database"]["drop_config"]["absolute_path"] - self.SPACE_NAMES = ["abci_chains", "assets", "blocks", "blocks_tx", - "elections", "meta_data", "pre_commits", "validators", - "transactions", "inputs", "outputs", "keys"] + self.SPACE_NAMES = [ + "abci_chains", + "assets", + "blocks", + "blocks_tx", + "elections", + "meta_data", + "pre_commits", + "validators", + "transactions", + "inputs", + "outputs", + "keys", + ] except tarantool.error.NetworkError as network_err: - logger.info('Host cant be reached') + logger.info("Host cant be reached") raise network_err except: - logger.info('Exception in _connect(): {}') + logger.info("Exception in _connect(): {}") raise ConfigurationError def query(self): @@ -75,11 +90,14 @@ class TarantoolDBConnection(Connection): def run_command(self, command: str, config: dict): from subprocess import run + print(f" commands: {command}") host_port = "%s:%s" % (self.host, self.port) execute_cmd = self._file_content_to_bytes(path=command) - output = run(["tarantoolctl", "connect", host_port], - input=execute_cmd, - capture_output=True).stderr + output = run( + ["tarantoolctl", "connect", host_port], + input=execute_cmd, + capture_output=True, + ).stderr output = output.decode() return output diff --git a/planetmint/commands/planetmint.py b/planetmint/commands/planetmint.py index 4ee43de..0fc9f12 100644 --- a/planetmint/commands/planetmint.py +++ b/planetmint/commands/planetmint.py @@ -16,12 +16,12 @@ import sys from planetmint.backend.tarantool.connection import TarantoolDBConnection from planetmint.core import rollback -from planetmint.migrations.chain_migration_election import ChainMigrationElection from planetmint.utils import load_node_key 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 +from planetmint.transactions.types.elections.chain_migration_election import ChainMigrationElection import planetmint from planetmint import (backend, ValidatorElection, Planetmint) diff --git a/planetmint/start.py b/planetmint/start.py index 7eeb022..d4efa84 100644 --- a/planetmint/start.py +++ b/planetmint/start.py @@ -36,7 +36,7 @@ BANNER = """ def start(args): # Exchange object for event stream api - logger.info('Starting Planetmint') + logger.info("Starting Planetmint") exchange = Exchange() # start the web api app_server = server.create_server( @@ -49,13 +49,15 @@ def start(args): logger.info(BANNER.format(Config().get()['server']['bind'])) # start websocket server - p_websocket_server = Process(name='planetmint_ws', - target=websocket_server.start, - daemon=True, - args=(exchange.get_subscriber_queue(EventTypes.BLOCK_VALID),)) + p_websocket_server = Process( + name="planetmint_ws", + target=websocket_server.start, + daemon=True, + args=(exchange.get_subscriber_queue(EventTypes.BLOCK_VALID),), + ) p_websocket_server.start() - p_exchange = Process(name='planetmint_exchange', target=exchange.run, daemon=True) + p_exchange = Process(name="planetmint_exchange", target=exchange.run, daemon=True) p_exchange.start() # We need to import this after spawning the web server @@ -63,7 +65,7 @@ def start(args): # for gevent. from abci.server import ABCIServer - setproctitle.setproctitle('planetmint') + setproctitle.setproctitle("planetmint") # Start the ABCIServer if args.experimental_parallel_validation: @@ -81,5 +83,5 @@ def start(args): app.run() -if __name__ == '__main__': +if __name__ == "__main__": start() diff --git a/planetmint/transactions/common/output.py b/planetmint/transactions/common/output.py index 7c7c1ef..df79b1d 100644 --- a/planetmint/transactions/common/output.py +++ b/planetmint/transactions/common/output.py @@ -6,7 +6,8 @@ from functools import reduce import base58 -from cryptoconditions import Fulfillment, ThresholdSha256, Ed25519Sha256 +from cryptoconditions import ThresholdSha256, Ed25519Sha256, ZenroomSha256 +from cryptoconditions import Fulfillment from planetmint.transactions.common.exceptions import AmountError from .utils import _fulfillment_to_details, _fulfillment_from_details @@ -24,30 +25,30 @@ class Output(object): owners before a Transaction was confirmed. """ - MAX_AMOUNT = 9 * 10 ** 18 + 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. + 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`. + 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') + raise TypeError("`public_keys` must be a list instance or None") if not isinstance(amount, int): - raise TypeError('`amount` must be an int') + raise TypeError("`amount` must be an int") if amount < 1: - raise AmountError('`amount` must be greater than 0') + raise AmountError("`amount` must be greater than 0") if amount > self.MAX_AMOUNT: - raise AmountError('`amount` must be <= %s' % self.MAX_AMOUNT) + raise AmountError("`amount` must be <= %s" % self.MAX_AMOUNT) self.fulfillment = fulfillment self.amount = amount @@ -60,30 +61,31 @@ class Output(object): 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. + Note: + A dictionary serialization of the Input the Output was + derived from is always provided. - Returns: - dict: The Output as an alternative serialization format. + 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) + # TODO verify if a script is returned in case of zenroom fulfillments + condition["details"] = _fulfillment_to_details(self.fulfillment) except AttributeError: pass try: - condition['uri'] = self.fulfillment.condition_uri + condition["uri"] = self.fulfillment.condition_uri except AttributeError: - condition['uri'] = self.fulfillment + condition["uri"] = self.fulfillment output = { - 'public_keys': self.public_keys, - 'condition': condition, - 'amount': str(self.amount), + "public_keys": self.public_keys, + "condition": condition, + "amount": str(self.amount), } return output @@ -91,66 +93,65 @@ class Output(object): 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: + 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)*, ...], ...] + [(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. + 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. + 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. + 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') + raise TypeError("`amount` must be a int") if amount < 1: - raise AmountError('`amount` needs to be greater than zero') + 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') + 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') + 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] + elif isinstance(public_keys[0], ZenroomSha256): + ffill = ZenroomSha256(public_key=base58.b58decode(public_keys[0])) else: - ffill = Ed25519Sha256( - public_key=base58.b58decode(public_keys[0])) + 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) + 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`. + 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. + 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`: + Returns: + :class:`cryptoconditions.ThresholdSha256`: """ try: threshold = len(new_public_keys) @@ -161,7 +162,7 @@ class Output(object): 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') + raise ValueError("Sublist cannot contain single owner") else: try: new_public_keys = new_public_keys.pop() @@ -176,8 +177,7 @@ class Output(object): if isinstance(new_public_keys, Fulfillment): ffill = new_public_keys else: - ffill = Ed25519Sha256( - public_key=base58.b58decode(new_public_keys)) + ffill = Ed25519Sha256(public_key=base58.b58decode(new_public_keys)) initial.add_subfulfillment(ffill) return initial @@ -185,25 +185,25 @@ class Output(object): 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. + 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. + Args: + data (dict): The dict to be transformed. - Returns: - :class:`~planetmint.transactions.common.transaction.Output` + Returns: + :class:`~planetmint.transactions.common.transaction.Output` """ try: - fulfillment = _fulfillment_from_details(data['condition']['details']) + fulfillment = _fulfillment_from_details(data["condition"]["details"]) except KeyError: # NOTE: Hashlock condition case - fulfillment = data['condition']['uri'] + fulfillment = data["condition"]["uri"] try: - amount = int(data['amount']) + amount = int(data["amount"]) except ValueError: - raise AmountError('Invalid amount: %s' % data['amount']) - return cls(fulfillment, data['public_keys'], amount) + raise AmountError("Invalid amount: %s" % data["amount"]) + return cls(fulfillment, data["public_keys"], amount) diff --git a/planetmint/transactions/common/schema/v2.0/transaction.yaml b/planetmint/transactions/common/schema/v2.0/transaction.yaml index 604302f..c09c6f2 100644 --- a/planetmint/transactions/common/schema/v2.0/transaction.yaml +++ b/planetmint/transactions/common/schema/v2.0/transaction.yaml @@ -100,8 +100,8 @@ definitions: 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}$" + (fpt=(ed25519|threshold|zenroom)-sha-256(&)?|cost=[0-9]+(&)?|\ + subtypes=(ed25519|zenroom)-sha-256(&)?){2,3}$" public_keys: "$ref": "#/definitions/public_keys" input: @@ -147,7 +147,7 @@ definitions: properties: type: type: string - pattern: "^ed25519-sha-256$" + pattern: "^(ed25519|zenroom)-sha-256$" public_key: "$ref": "#/definitions/base58" - type: object diff --git a/planetmint/transactions/common/transaction.py b/planetmint/transactions/common/transaction.py index 6c744f7..5b88b09 100644 --- a/planetmint/transactions/common/transaction.py +++ b/planetmint/transactions/common/transaction.py @@ -17,9 +17,8 @@ from functools import lru_cache import rapidjson import base58 -from cryptoconditions import Fulfillment, ThresholdSha256, Ed25519Sha256 -from cryptoconditions.exceptions import ( - ParsingError, ASN1DecodeError, ASN1EncodeError, UnsupportedTypeError) +from cryptoconditions import Fulfillment, ThresholdSha256, Ed25519Sha256, ZenroomSha256 +from cryptoconditions.exceptions import ParsingError, ASN1DecodeError, ASN1EncodeError try: from hashlib import sha3_256 @@ -28,8 +27,14 @@ except ImportError: from planetmint.transactions.common.crypto import PrivateKey, hash_data from planetmint.transactions.common.exceptions import ( - KeypairMismatchException, InputDoesNotExist, DoubleSpend, - InvalidHash, InvalidSignature, AmountError, AssetIdMismatch) + 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 @@ -37,92 +42,113 @@ from .output import Output from .transaction_link import TransactionLink UnspentOutput = namedtuple( - 'UnspentOutput', ( + "UnspentOutput", + ( # TODO 'utxo_hash': sha3_256(f'{txid}{output_index}'.encode()) # 'utxo_hash', # noqa - 'transaction_id', - 'output_index', - 'amount', - 'asset_id', - 'condition_uri', - ) + "transaction_id", + "output_index", + "amount", + "asset_id", + "condition_uri", + ), ) class Transaction(object): """A Transaction is used to create and transfer assets. - Note: - For adding Inputs and Outputs, this class provides methods - to do so. + Note: + For adding Inputs and Outputs, this class provides methods + to do so. - Attributes: - operation (str): Defines the operation of the Transaction. - inputs (:obj:`list` of :class:`~planetmint.transactions.common. - transaction.Input`, optional): Define the assets to - spend. - 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`` - property while ``TRANSFER`` Transactions require a dict with a - ``id`` property. - metadata (dict): - Metadata to be stored along with the Transaction. - version (string): Defines the version number of a Transaction. + Attributes: + operation (str): Defines the operation of the Transaction. + inputs (:obj:`list` of :class:`~planetmint.transactions.common. + transaction.Input`, optional): Define the assets to + spend. + 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`` + property while ``TRANSFER`` Transactions require a dict with a + ``id`` property. + metadata (dict): + Metadata to be stored along with the Transaction. + version (string): Defines the version number of a Transaction. """ - CREATE = 'CREATE' - TRANSFER = 'TRANSFER' + CREATE = "CREATE" + TRANSFER = "TRANSFER" ALLOWED_OPERATIONS = (CREATE, TRANSFER) - VERSION = '2.0' + VERSION = "2.0" - def __init__(self, operation, asset, inputs=None, outputs=None, - metadata=None, version=None, hash_id=None, tx_dict=None): + def __init__( + self, + operation, + asset, + inputs=None, + outputs=None, + metadata=None, + version=None, + hash_id=None, + tx_dict=None, + ): """The constructor allows to create a customizable Transaction. - Note: - When no `version` is provided, one is being - generated by this method. + Note: + When no `version` is provided, one is being + generated by this method. - Args: - operation (str): Defines the operation of the Transaction. - asset (dict): Asset payload for this Transaction. - inputs (:obj:`list` of :class:`~planetmint.transactions.common. - transaction.Input`, optional): Define the assets to - 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 - Transaction. - version (string): Defines the version number of a Transaction. - hash_id (string): Hash id of the transaction. + Args: + operation (str): Defines the operation of the Transaction. + asset (dict): Asset payload for this Transaction. + inputs (:obj:`list` of :class:`~planetmint.transactions.common. + transaction.Input`, optional): Define the assets to + 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 + Transaction. + version (string): Defines the version number of a Transaction. + hash_id (string): Hash id of the transaction. """ if operation not in self.ALLOWED_OPERATIONS: - allowed_ops = ', '.join(self.__class__.ALLOWED_OPERATIONS) - raise ValueError('`operation` must be one of {}' - .format(allowed_ops)) + allowed_ops = ", ".join(self.__class__.ALLOWED_OPERATIONS) + raise ValueError("`operation` must be one of {}".format(allowed_ops)) # Asset payloads for 'CREATE' operations must be None or # dicts holding a `data` property. Asset payloads for 'TRANSFER' # operations must be dicts holding an `id` property. - if (operation == self.CREATE and - asset is not None and not (isinstance(asset, dict) and 'data' in asset)): - raise TypeError(('`asset` must be None or a dict holding a `data` ' - " property instance for '{}' Transactions".format(operation))) - elif (operation == self.TRANSFER and - not (isinstance(asset, dict) and 'id' in asset)): - raise TypeError(('`asset` must be a dict holding an `id` property ' - 'for \'TRANSFER\' Transactions')) + if ( + operation == self.CREATE + and asset is not None + and not (isinstance(asset, dict) and "data" in asset) + ): + raise TypeError( + ( + "`asset` must be None or a dict holding a `data` " + " property instance for '{}' Transactions".format(operation) + ) + ) + elif operation == self.TRANSFER and not ( + isinstance(asset, dict) and "id" in asset + ): + raise TypeError( + ( + "`asset` must be a dict holding an `id` property " + "for 'TRANSFER' Transactions" + ) + ) if outputs and not isinstance(outputs, list): - raise TypeError('`outputs` must be a list instance or None') + raise TypeError("`outputs` must be a list instance or None") if inputs and not isinstance(inputs, list): - raise TypeError('`inputs` must be a list instance or None') + raise TypeError("`inputs` must be a list instance or None") if metadata is not None and not isinstance(metadata, dict): - raise TypeError('`metadata` must be a dict or None') + raise TypeError("`metadata` must be a dict or None") self.version = version if version is not None else self.VERSION self.operation = operation @@ -142,14 +168,17 @@ class Transaction(object): if self.operation == self.CREATE: self._asset_id = self._id elif self.operation == self.TRANSFER: - self._asset_id = self.asset['id'] - return (UnspentOutput( - transaction_id=self._id, - output_index=output_index, - amount=output.amount, - asset_id=self._asset_id, - condition_uri=output.fulfillment.condition_uri, - ) for output_index, output in enumerate(self.outputs)) + self._asset_id = self.asset["id"] + return ( + UnspentOutput( + transaction_id=self._id, + output_index=output_index, + amount=output.amount, + asset_id=self._asset_id, + condition_uri=output.fulfillment.condition_uri, + ) + for output_index, output in enumerate(self.outputs) + ) @property def spent_outputs(self): @@ -157,10 +186,7 @@ class Transaction(object): is represented as a dictionary containing a transaction id and output index. """ - return ( - input_.fulfills.to_dict() - for input_ in self.inputs if input_.fulfills - ) + return (input_.fulfills.to_dict() for input_ in self.inputs if input_.fulfills) @property def serialized(self): @@ -179,80 +205,83 @@ class Transaction(object): def to_inputs(self, indices=None): """Converts a Transaction's outputs to spendable inputs. - Note: - Takes the Transaction's outputs and derives inputs - from that can then be passed into `Transaction.transfer` as - `inputs`. - A list of integers can be passed to `indices` that - defines which outputs should be returned as inputs. - If no `indices` are passed (empty list or None) all - outputs of the Transaction are returned. + Note: + Takes the Transaction's outputs and derives inputs + from that can then be passed into `Transaction.transfer` as + `inputs`. + A list of integers can be passed to `indices` that + defines which outputs should be returned as inputs. + If no `indices` are passed (empty list or None) all + outputs of the Transaction are returned. - Args: - indices (:obj:`list` of int): Defines which - outputs should be returned as inputs. + Args: + indices (:obj:`list` of int): Defines which + outputs should be returned as inputs. - Returns: - :obj:`list` of :class:`~planetmint.transactions.common.transaction. - Input` + Returns: + :obj:`list` of :class:`~planetmint.transactions.common.transaction. + Input` """ # NOTE: If no indices are passed, we just assume to take all outputs # as inputs. indices = indices or range(len(self.outputs)) return [ - Input(self.outputs[idx].fulfillment, - self.outputs[idx].public_keys, - TransactionLink(self.id, idx)) + Input( + self.outputs[idx].fulfillment, + self.outputs[idx].public_keys, + TransactionLink(self.id, idx), + ) for idx in indices ] def add_input(self, input_): """Adds an input to a Transaction's list of inputs. - Args: - input_ (:class:`~planetmint.transactions.common.transaction. - Input`): An Input to be added to the Transaction. + Args: + input_ (:class:`~planetmint.transactions.common.transaction. + Input`): An Input to be added to the Transaction. """ if not isinstance(input_, Input): - raise TypeError('`input_` must be a Input instance') + raise TypeError("`input_` must be a Input instance") self.inputs.append(input_) def add_output(self, output): """Adds an output to a Transaction's list of outputs. - Args: - output (:class:`~planetmint.transactions.common.transaction. - Output`): An Output to be added to the - Transaction. + Args: + output (:class:`~planetmint.transactions.common.transaction. + Output`): An Output to be added to the + Transaction. """ if not isinstance(output, Output): - raise TypeError('`output` must be an Output instance or None') + raise TypeError("`output` must be an Output instance or None") self.outputs.append(output) def sign(self, private_keys): """Fulfills a previous Transaction's Output by signing Inputs. - Note: - This method works only for the following Cryptoconditions - currently: - - Ed25519Fulfillment - - ThresholdSha256 - Furthermore, note that all keys required to fully sign the - Transaction have to be passed to this method. A subset of all - will cause this method to fail. + Note: + This method works only for the following Cryptoconditions + currently: + - Ed25519Fulfillment + - ThresholdSha256 + - ZenroomSha256 + Furthermore, note that all keys required to fully sign the + Transaction have to be passed to this method. A subset of all + will cause this method to fail. - Args: - private_keys (:obj:`list` of :obj:`str`): A complete list of - all private keys needed to sign all Fulfillments of this - Transaction. + Args: + private_keys (:obj:`list` of :obj:`str`): A complete list of + all private keys needed to sign all Fulfillments of this + Transaction. - Returns: - :class:`~planetmint.transactions.common.transaction.Transaction` + Returns: + :class:`~planetmint.transactions.common.transaction.Transaction` """ # TODO: Singing should be possible with at least one of all private # keys supplied to this method. if private_keys is None or not isinstance(private_keys, list): - raise TypeError('`private_keys` must be a list instance') + raise TypeError("`private_keys` must be a list instance") # NOTE: Generate public keys from private keys and match them in a # dictionary: @@ -269,8 +298,10 @@ class Transaction(object): # to decode to convert the bytestring into a python str return public_key.decode() - key_pairs = {gen_public_key(PrivateKey(private_key)): - PrivateKey(private_key) for private_key in private_keys} + key_pairs = { + gen_public_key(PrivateKey(private_key)): PrivateKey(private_key) + for private_key in private_keys + } tx_dict = self.to_dict() tx_dict = Transaction._remove_signatures(tx_dict) @@ -286,38 +317,39 @@ class Transaction(object): def _sign_input(cls, input_, message, key_pairs): """Signs a single Input. - Note: - This method works only for the following Cryptoconditions - currently: - - Ed25519Fulfillment - - ThresholdSha256. - - Args: - 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. + Note: + This method works only for the following Cryptoconditions + currently: + - Ed25519Fulfillment + - ThresholdSha256. + - ZenroomSha256 + Args: + 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. """ if isinstance(input_.fulfillment, Ed25519Sha256): - return cls._sign_simple_signature_fulfillment(input_, message, - key_pairs) + return cls._sign_simple_signature_fulfillment(input_, message, key_pairs) elif isinstance(input_.fulfillment, ThresholdSha256): - return cls._sign_threshold_signature_fulfillment(input_, message, - key_pairs) + return cls._sign_threshold_signature_fulfillment(input_, message, key_pairs) + elif isinstance(input_.fulfillment, ZenroomSha256): + return cls._sign_threshold_signature_fulfillment(input_, message, key_pairs) else: raise ValueError( - 'Fulfillment couldn\'t be matched to ' - 'Cryptocondition fulfillment type.') + "Fulfillment couldn't be matched to " + "Cryptocondition fulfillment type." + ) @classmethod - def _sign_simple_signature_fulfillment(cls, input_, message, key_pairs): - """Signs a Ed25519Fulfillment. + def _sign_zenroom_fulfillment(cls, input_, message, key_pairs): + """Signs a Zenroomful. - Args: - 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. + Args: + 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. """ # NOTE: To eliminate the dangers of accidentally signing a condition by # reference, we remove the reference of input_ here @@ -327,35 +359,74 @@ class Transaction(object): public_key = input_.owners_before[0] message = sha3_256(message.encode()) if input_.fulfills: - message.update('{}{}'.format( - input_.fulfills.txid, input_.fulfills.output).encode()) + message.update( + "{}{}".format(input_.fulfills.txid, input_.fulfills.output).encode() + ) try: # cryptoconditions makes no assumptions of the encoding of the # message to sign or verify. It only accepts bytestrings input_.fulfillment.sign( - message.digest(), base58.b58decode(key_pairs[public_key].encode())) + message.digest(), base58.b58decode(key_pairs[public_key].encode()) + ) except KeyError: - raise KeypairMismatchException('Public key {} is not a pair to ' - 'any of the private keys' - .format(public_key)) + raise KeypairMismatchException( + "Public key {} is not a pair to " + "any of the private keys".format(public_key) + ) + return input_ + + @classmethod + def _sign_simple_signature_fulfillment(cls, input_, message, key_pairs): + """Signs a Ed25519Fulfillment. + + Args: + 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. + """ + # NOTE: To eliminate the dangers of accidentally signing a condition by + # reference, we remove the reference of input_ here + # intentionally. If the user of this class knows how to use it, + # this should never happen, but then again, never say never. + input_ = deepcopy(input_) + public_key = input_.owners_before[0] + message = sha3_256(message.encode()) + if input_.fulfills: + message.update( + "{}{}".format(input_.fulfills.txid, input_.fulfills.output).encode() + ) + + try: + # cryptoconditions makes no assumptions of the encoding of the + # message to sign or verify. It only accepts bytestrings + input_.fulfillment.sign( + message.digest(), base58.b58decode(key_pairs[public_key].encode()) + ) + except KeyError: + raise KeypairMismatchException( + "Public key {} is not a pair to " + "any of the private keys".format(public_key) + ) return input_ @classmethod def _sign_threshold_signature_fulfillment(cls, input_, message, key_pairs): """Signs a ThresholdSha256. - Args: - 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. + Args: + 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. """ input_ = deepcopy(input_) message = sha3_256(message.encode()) if input_.fulfills: - message.update('{}{}'.format( - input_.fulfills.txid, input_.fulfills.output).encode()) + message.update( + "{}{}".format(input_.fulfills.txid, input_.fulfills.output).encode() + ) for owner_before in set(input_.owners_before): # TODO: CC should throw a KeypairMismatchException, instead of @@ -368,24 +439,24 @@ class Transaction(object): # TODO FOR CC: `get_subcondition` is singular. One would not # expect to get a list back. ccffill = input_.fulfillment - subffills = ccffill.get_subcondition_from_vk( - base58.b58decode(owner_before)) + subffills = ccffill.get_subcondition_from_vk(base58.b58decode(owner_before)) if not subffills: - raise KeypairMismatchException('Public key {} cannot be found ' - 'in the fulfillment' - .format(owner_before)) + raise KeypairMismatchException( + "Public key {} cannot be found " + "in the fulfillment".format(owner_before) + ) try: private_key = key_pairs[owner_before] except KeyError: - raise KeypairMismatchException('Public key {} is not a pair ' - 'to any of the private keys' - .format(owner_before)) + raise KeypairMismatchException( + "Public key {} is not a pair " + "to any of the private keys".format(owner_before) + ) # cryptoconditions makes no assumptions of the encoding of the # message to sign or verify. It only accepts bytestrings for subffill in subffills: - subffill.sign( - message.digest(), base58.b58decode(private_key.encode())) + subffill.sign(message.digest(), base58.b58decode(private_key.encode())) return input_ def inputs_valid(self, outputs=None): @@ -410,72 +481,84 @@ class Transaction(object): # to check for outputs, we're just submitting dummy # values to the actual method. This simplifies it's logic # greatly, as we do not have to check against `None` values. - return self._inputs_valid(['dummyvalue' - for _ in self.inputs]) + return self._inputs_valid(["dummyvalue" for _ in self.inputs]) elif self.operation == self.TRANSFER: - return self._inputs_valid([output.fulfillment.condition_uri - for output in outputs]) + return self._inputs_valid( + [output.fulfillment.condition_uri for output in outputs] + ) else: - allowed_ops = ', '.join(self.__class__.ALLOWED_OPERATIONS) - raise TypeError('`operation` must be one of {}' - .format(allowed_ops)) + allowed_ops = ", ".join(self.__class__.ALLOWED_OPERATIONS) + raise TypeError("`operation` must be one of {}".format(allowed_ops)) def _inputs_valid(self, output_condition_uris): """Validates an Input against a given set of Outputs. - Note: - The number of `output_condition_uris` must be equal to the - number of Inputs a Transaction has. + Note: + The number of `output_condition_uris` must be equal to the + number of Inputs a Transaction has. - Args: - output_condition_uris (:obj:`list` of :obj:`str`): A list of - Outputs to check the Inputs against. + Args: + output_condition_uris (:obj:`list` of :obj:`str`): A list of + Outputs to check the Inputs against. - Returns: - bool: If all Outputs are valid. + Returns: + bool: If all Outputs are valid. """ if len(self.inputs) != len(output_condition_uris): - raise ValueError('Inputs and ' - 'output_condition_uris must have the same count') + raise ValueError( + "Inputs and " "output_condition_uris must have the same count" + ) tx_dict = self.tx_dict if self.tx_dict else self.to_dict() tx_dict = Transaction._remove_signatures(tx_dict) - tx_dict['id'] = None + tx_dict["id"] = None tx_serialized = Transaction._to_str(tx_dict) def validate(i, output_condition_uri=None): """Validate input against output condition URI""" - return self._input_valid(self.inputs[i], self.operation, - tx_serialized, output_condition_uri) + return self._input_valid( + self.inputs[i], self.operation, tx_serialized, output_condition_uri + ) - return all(validate(i, cond) - for i, cond in enumerate(output_condition_uris)) + return all(validate(i, cond) for i, cond in enumerate(output_condition_uris)) @lru_cache(maxsize=16384) def _input_valid(self, input_, operation, message, output_condition_uri=None): """Validates a single Input against a single Output. - Note: - In case of a `CREATE` Transaction, this method - does not validate against `output_condition_uri`. + Note: + In case of a `CREATE` Transaction, this method + does not validate against `output_condition_uri`. - Args: - input_ (:class:`~planetmint.transactions.common.transaction. - Input`) The Input to be signed. - operation (str): The type of Transaction. - message (str): The fulfillment message. - output_condition_uri (str, optional): An Output to check the - Input against. + Args: + input_ (:class:`~planetmint.transactions.common.transaction. + Input`) The Input to be signed. + operation (str): The type of Transaction. + message (str): The fulfillment message. + output_condition_uri (str, optional): An Output to check the + Input against. - Returns: - bool: If the Input is valid. + Returns: + bool: If the Input is valid. """ ccffill = input_.fulfillment try: parsed_ffill = Fulfillment.from_uri(ccffill.serialize_uri()) - except (TypeError, ValueError, - ParsingError, ASN1DecodeError, ASN1EncodeError): + except TypeError as e: + print(f"Exception TypeError : {e}") + return False + except ValueError as e: + print(f"Exception ValueError : {e}") + return False + except ParsingError as e: + print(f"Exception ParsingError : {e}") + return False + except ASN1DecodeError as e: + print(f"Exception ASN1DecodeError : {e}") + return False + except ASN1EncodeError as e: + print(f"Exception ASN1EncodeError : {e}") return False if operation == self.CREATE: @@ -485,17 +568,22 @@ class Transaction(object): else: output_valid = output_condition_uri == ccffill.condition_uri - message = sha3_256(message.encode()) - if input_.fulfills: - message.update('{}{}'.format( - input_.fulfills.txid, input_.fulfills.output).encode()) + ffill_valid = False + if isinstance(parsed_ffill, ZenroomSha256): + ffill_valid = parsed_ffill.validate(message=message) + else: + message = sha3_256(message.encode()) + if input_.fulfills: + message.update( + "{}{}".format(input_.fulfills.txid, input_.fulfills.output).encode() + ) - # NOTE: We pass a timestamp to `.validate`, as in case of a timeout - # condition we'll have to validate against it + # NOTE: We pass a timestamp to `.validate`, as in case of a timeout + # condition we'll have to validate against it - # cryptoconditions makes no assumptions of the encoding of the - # message to sign or verify. It only accepts bytestrings - ffill_valid = parsed_ffill.validate(message=message.digest()) + # cryptoconditions makes no assumptions of the encoding of the + # message to sign or verify. It only accepts bytestrings + ffill_valid = parsed_ffill.validate(message=message.digest()) return output_valid and ffill_valid # This function is required by `lru_cache` to create a key for memoization @@ -506,17 +594,17 @@ class Transaction(object): def to_dict(self): """Transforms the object to a Python dictionary. - Returns: - dict: The Transaction as an alternative serialization format. + Returns: + dict: The Transaction as an alternative serialization format. """ return { - 'inputs': [input_.to_dict() for input_ in self.inputs], - 'outputs': [output.to_dict() for output in self.outputs], - 'operation': str(self.operation), - 'metadata': self.metadata, - 'asset': self.asset, - 'version': self.version, - 'id': self._id, + "inputs": [input_.to_dict() for input_ in self.inputs], + "outputs": [output.to_dict() for output in self.outputs], + "operation": str(self.operation), + "metadata": self.metadata, + "asset": self.asset, + "version": self.version, + "id": self._id, } @staticmethod @@ -524,22 +612,22 @@ class Transaction(object): def _remove_signatures(tx_dict): """Takes a Transaction dictionary and removes all signatures. - Args: - tx_dict (dict): The Transaction to remove all signatures from. + Args: + tx_dict (dict): The Transaction to remove all signatures from. - Returns: - dict + Returns: + dict """ # NOTE: We remove the reference since we need `tx_dict` only for the # transaction's hash tx_dict = deepcopy(tx_dict) - for input_ in tx_dict['inputs']: + for input_ in tx_dict["inputs"]: # NOTE: Not all Cryptoconditions return a `signature` key (e.g. # ThresholdSha256), so setting it to `None` in any # case could yield incorrect signatures. This is why we only # set it to `None` if it's set in the dict. - input_['fulfillment'] = None + input_["fulfillment"] = None return tx_dict @staticmethod @@ -551,7 +639,7 @@ class Transaction(object): return self._id def to_hash(self): - return self.to_dict()['id'] + return self.to_dict()["id"] @staticmethod def _to_str(value): @@ -588,39 +676,46 @@ class Transaction(object): transactions = [transactions] # create a set of the transactions' asset ids - asset_ids = {tx.id if tx.operation == tx.CREATE - else tx.asset['id'] - for tx in transactions} + asset_ids = { + tx.id if tx.operation == tx.CREATE else tx.asset["id"] + for tx in transactions + } # check that all the transasctions have the same asset id if len(asset_ids) > 1: - raise AssetIdMismatch(('All inputs of all transactions passed' - ' need to have the same asset id')) + raise AssetIdMismatch( + ( + "All inputs of all transactions passed" + " need to have the same asset id" + ) + ) return asset_ids.pop() @staticmethod def validate_id(tx_body): """Validate the transaction ID of a transaction - Args: - tx_body (dict): The Transaction to be transformed. + Args: + tx_body (dict): The Transaction to be transformed. """ # NOTE: Remove reference to avoid side effects tx_body = deepcopy(tx_body) tx_body = rapidjson.loads(rapidjson.dumps(tx_body)) try: - proposed_tx_id = tx_body['id'] + proposed_tx_id = tx_body["id"] except KeyError: - raise InvalidHash('No transaction id found!') + raise InvalidHash("No transaction id found!") + + tx_body["id"] = None - tx_body['id'] = None - #tx_body = Transaction._remove_signatures(tx_body) tx_body_serialized = Transaction._to_str(tx_body) valid_tx_id = Transaction._to_hash(tx_body_serialized) if proposed_tx_id != valid_tx_id: - err_msg= ("The transaction's id '{}' isn't equal to " - "the hash of its body, i.e. it's not valid.") + err_msg = ( + "The transaction's id '{}' isn't equal to " + "the hash of its body, i.e. it's not valid." + ) raise InvalidHash(err_msg.format(proposed_tx_id)) @classmethod @@ -628,13 +723,17 @@ class Transaction(object): def from_dict(cls, tx, skip_schema_validation=True): """Transforms a Python dictionary to a Transaction object. - Args: - tx_body (dict): The Transaction to be transformed. + Args: + tx_body (dict): The Transaction to be transformed. - Returns: - :class:`~planetmint.transactions.common.transaction.Transaction` + Returns: + :class:`~planetmint.transactions.common.transaction.Transaction` """ - operation = tx.get('operation', Transaction.CREATE) if isinstance(tx, dict) else Transaction.CREATE + operation = ( + tx.get("operation", Transaction.CREATE) + if isinstance(tx, dict) + else Transaction.CREATE + ) cls = Transaction.resolve_class(operation) id = None @@ -642,13 +741,13 @@ class Transaction(object): id = tx['id'] except KeyError: id = None - #tx['asset'] = tx['asset'][0] if isinstance( tx['asset'], list) or isinstance( tx['asset'], tuple) else tx['asset'], - local_dict= { + # tx['asset'] = tx['asset'][0] if isinstance( tx['asset'], list) or isinstance( tx['asset'], tuple) else tx['asset'], + local_dict = { 'inputs': tx['inputs'], 'outputs': tx['outputs'], 'operation': operation, 'metadata': tx['metadata'], - 'asset': tx['asset'],#[0] if isinstance( tx['asset'], list) or isinstance( tx['asset'], tuple) else tx['asset'], + 'asset': tx['asset'], # [0] if isinstance( tx['asset'], list) or isinstance( tx['asset'], tuple) else tx['asset'], 'version': tx['version'], 'id': id } @@ -657,10 +756,18 @@ class Transaction(object): cls.validate_id(local_dict) cls.validate_schema(local_dict) - inputs = [Input.from_dict(input_) for input_ in tx['inputs']] - outputs = [Output.from_dict(output) for output in tx['outputs']] - return cls(tx['operation'], tx['asset'], inputs, outputs, - tx['metadata'], tx['version'], hash_id=tx['id'], tx_dict=tx) + inputs = [Input.from_dict(input_) for input_ in tx["inputs"]] + outputs = [Output.from_dict(output) for output in tx["outputs"]] + return cls( + tx["operation"], + tx["asset"], + inputs, + outputs, + tx["metadata"], + tx["version"], + hash_id=tx["id"], + tx_dict=tx, + ) @classmethod def from_db(cls, planet, tx_dict_list): @@ -686,9 +793,9 @@ class Transaction(object): tx_map = {} tx_ids = [] for tx in tx_dict_list: - tx.update({'metadata': None}) - tx_map[tx['id']] = tx - tx_ids.append(tx['id']) + tx.update({"metadata": None}) + tx_map[tx["id"]] = tx + tx_ids.append(tx["id"]) assets = list(planet.get_assets(tx_ids)) for asset in assets: @@ -742,14 +849,13 @@ class Transaction(object): input_tx = ctxn if input_tx is None: - raise InputDoesNotExist("input `{}` doesn't exist" - .format(input_txid)) + raise InputDoesNotExist("input `{}` doesn't exist".format(input_txid)) - spent = planet.get_spent(input_txid, input_.fulfills.output, - current_transactions) + spent = planet.get_spent( + input_txid, input_.fulfills.output, current_transactions + ) if spent: - raise DoubleSpend('input `{}` was already spent' - .format(input_txid)) + raise DoubleSpend("input `{}` was already spent".format(input_txid)) output = input_tx.outputs[input_.fulfills.output] input_conditions.append(output) @@ -762,21 +868,32 @@ class Transaction(object): # validate asset id asset_id = self.get_asset_id(input_txs) - if asset_id != self.asset['id']: - raise AssetIdMismatch(('The asset id of the input does not' - ' match the asset id of the' - ' transaction')) + if asset_id != self.asset["id"]: + raise AssetIdMismatch( + ( + "The asset id of the input does not" + " match the asset id of the" + " transaction" + ) + ) - input_amount = sum([input_condition.amount for input_condition in input_conditions]) - output_amount = sum([output_condition.amount for output_condition in self.outputs]) + input_amount = sum( + [input_condition.amount for input_condition in input_conditions] + ) + output_amount = sum( + [output_condition.amount for output_condition in self.outputs] + ) if output_amount != input_amount: - raise AmountError(('The amount used in the inputs `{}`' - ' needs to be same as the amount used' - ' in the outputs `{}`') - .format(input_amount, output_amount)) + raise AmountError( + ( + "The amount used in the inputs `{}`" + " needs to be same as the amount used" + " in the outputs `{}`" + ).format(input_amount, output_amount) + ) if not self.inputs_valid(input_conditions): - raise InvalidSignature('Transaction signature is invalid.') + raise InvalidSignature("Transaction signature is invalid.") return True diff --git a/planetmint/transactions/common/utils.py b/planetmint/transactions/common/utils.py index 07a9273..94cc37a 100644 --- a/planetmint/transactions/common/utils.py +++ b/planetmint/transactions/common/utils.py @@ -10,17 +10,17 @@ import rapidjson from planetmint.config import Config from planetmint.transactions.common.exceptions import ValidationError -from cryptoconditions import ThresholdSha256, Ed25519Sha256 +from cryptoconditions import ThresholdSha256, Ed25519Sha256, ZenroomSha256 from planetmint.transactions.common.exceptions import ThresholdTooDeep from cryptoconditions.exceptions import UnsupportedTypeError def gen_timestamp(): """The Unix time, rounded to the nearest second. - See https://en.wikipedia.org/wiki/Unix_time + See https://en.wikipedia.org/wiki/Unix_time - Returns: - str: the Unix time + Returns: + str: the Unix time """ return str(round(time.time())) @@ -28,34 +28,33 @@ def gen_timestamp(): def serialize(data): """Serialize a dict into a JSON formatted string. - This function enforces rules like the separator and order of keys. - This ensures that all dicts are serialized in the same way. + This function enforces rules like the separator and order of keys. + This ensures that all dicts are serialized in the same way. - This is specially important for hashing data. We need to make sure that - everyone serializes their data in the same way so that we do not have - hash mismatches for the same structure due to serialization - differences. + This is specially important for hashing data. We need to make sure that + everyone serializes their data in the same way so that we do not have + hash mismatches for the same structure due to serialization + differences. - Args: - data (dict): dict to serialize + Args: + data (dict): dict to serialize - Returns: - str: JSON formatted string + Returns: + str: JSON formatted string """ - return rapidjson.dumps(data, skipkeys=False, ensure_ascii=False, - sort_keys=True) + return rapidjson.dumps(data, skipkeys=False, ensure_ascii=False, sort_keys=True) def deserialize(data): """Deserialize a JSON formatted string into a dict. - Args: - data (str): JSON formatted string. + Args: + data (str): JSON formatted string. - Returns: - dict: dict resulting from the serialization of a JSON formatted - string. + Returns: + dict: dict resulting from the serialization of a JSON formatted + string. """ return rapidjson.loads(data) @@ -63,22 +62,22 @@ def deserialize(data): def validate_txn_obj(obj_name, obj, key, validation_fun): """Validate value of `key` in `obj` using `validation_fun`. - Args: - obj_name (str): name for `obj` being validated. - obj (dict): dictionary object. - key (str): key to be validated in `obj`. - validation_fun (function): function used to validate the value - of `key`. + Args: + obj_name (str): name for `obj` being validated. + obj (dict): dictionary object. + key (str): key to be validated in `obj`. + validation_fun (function): function used to validate the value + of `key`. - Returns: - None: indicates validation successful + Returns: + None: indicates validation successful - Raises: - ValidationError: `validation_fun` will raise exception on failure + Raises: + ValidationError: `validation_fun` will raise exception on failure """ backend = Config().get()['database']['backend'] - if backend == 'localmongodb': + if backend == "localmongodb": data = obj.get(key, {}) if isinstance(data, dict): validate_all_keys_in_obj(obj_name, data, validation_fun) @@ -97,17 +96,17 @@ def validate_all_items_in_list(obj_name, data, validation_fun): def validate_all_keys_in_obj(obj_name, obj, validation_fun): """Validate all (nested) keys in `obj` by using `validation_fun`. - Args: - obj_name (str): name for `obj` being validated. - obj (dict): dictionary object. - validation_fun (function): function used to validate the value - of `key`. + Args: + obj_name (str): name for `obj` being validated. + obj (dict): dictionary object. + validation_fun (function): function used to validate the value + of `key`. - Returns: - None: indicates validation successful + Returns: + None: indicates validation successful - Raises: - ValidationError: `validation_fun` will raise this error on failure + Raises: + ValidationError: `validation_fun` will raise this error on failure """ for key, value in obj.items(): validation_fun(obj_name, key) @@ -119,16 +118,16 @@ def validate_all_keys_in_obj(obj_name, obj, validation_fun): def validate_all_values_for_key_in_obj(obj, key, validation_fun): """Validate value for all (nested) occurrence of `key` in `obj` - using `validation_fun`. + using `validation_fun`. - Args: - obj (dict): dictionary object. - key (str): key whose value is to be validated. - validation_fun (function): function used to validate the value - of `key`. + Args: + obj (dict): dictionary object. + key (str): key whose value is to be validated. + validation_fun (function): function used to validate the value + of `key`. - Raises: - ValidationError: `validation_fun` will raise this error on failure + Raises: + ValidationError: `validation_fun` will raise this error on failure """ for vkey, value in obj.items(): if vkey == key: @@ -150,22 +149,24 @@ def validate_all_values_for_key_in_list(input_list, key, validation_fun): def validate_key(obj_name, key): """Check if `key` contains ".", "$" or null characters. - https://docs.mongodb.com/manual/reference/limits/#Restrictions-on-Field-Names + https://docs.mongodb.com/manual/reference/limits/#Restrictions-on-Field-Names - Args: - obj_name (str): object name to use when raising exception - key (str): key to validated + Args: + obj_name (str): object name to use when raising exception + key (str): key to validated - Returns: - None: validation successful + Returns: + None: validation successful - Raises: - ValidationError: will raise exception in case of regex match. + Raises: + ValidationError: will raise exception in case of regex match. """ - if re.search(r'^[$]|\.|\x00', key): - error_str = ('Invalid key name "{}" in {} object. The ' - 'key name cannot contain characters ' - '".", "$" or null characters').format(key, obj_name) + if re.search(r"^[$]|\.|\x00", key): + error_str = ( + 'Invalid key name "{}" in {} object. The ' + "key name cannot contain characters " + '".", "$" or null characters' + ).format(key, obj_name) raise ValidationError(error_str) @@ -176,21 +177,26 @@ def _fulfillment_to_details(fulfillment): fulfillment: Crypto-conditions Fulfillment object """ - if fulfillment.type_name == 'ed25519-sha-256': + if fulfillment.type_name == "ed25519-sha-256": return { - 'type': 'ed25519-sha-256', - 'public_key': base58.b58encode(fulfillment.public_key).decode(), + "type": "ed25519-sha-256", + "public_key": base58.b58encode(fulfillment.public_key).decode(), } - if fulfillment.type_name == 'threshold-sha-256': + if fulfillment.type_name == "threshold-sha-256": subconditions = [ - _fulfillment_to_details(cond['body']) - for cond in fulfillment.subconditions + _fulfillment_to_details(cond["body"]) for cond in fulfillment.subconditions ] return { - 'type': 'threshold-sha-256', - 'threshold': fulfillment.threshold, - 'subconditions': subconditions, + "type": "threshold-sha-256", + "threshold": fulfillment.threshold, + "subconditions": subconditions, + } + if fulfillment.type_name == "zenroom-sha-256": + return { + "type": "zenroom-sha-256", + "public_key": base58.b58encode(fulfillment.public_key).decode(), + "script": base58.b58encode(fulfillment.script).decode(), } raise UnsupportedTypeError(fulfillment.type_name) @@ -205,15 +211,22 @@ def _fulfillment_from_details(data, _depth=0): if _depth == 100: raise ThresholdTooDeep() - if data['type'] == 'ed25519-sha-256': - public_key = base58.b58decode(data['public_key']) + 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']: + 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')) + if data["type"] == "zenroom-sha-256": + public_key = base58.b58decode(data["public_key"]) + script = base58.b58decode(data["script"]) + # zenroom = ZenroomSha256(script=script, data=None, keys={public_key}) + # TODO: assign to zenroom and evaluate the outcome + ZenroomSha256(script=script, data=None, keys={public_key}) + + raise UnsupportedTypeError(data.get("type")) diff --git a/planetmint/migrations/chain_migration_election.py b/planetmint/transactions/types/elections/chain_migration_election.py similarity index 100% rename from planetmint/migrations/chain_migration_election.py rename to planetmint/transactions/types/elections/chain_migration_election.py diff --git a/planetmint/version.py b/planetmint/version.py index ff63812..c87f33f 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.3' -__short_version__ = '0.9' +__version__ = "0.9.9" +__short_version__ = "0.9" # Supported Tendermint versions -__tm_supported_versions__ = ['0.34.15'] +__tm_supported_versions__ = ["0.34.15"] diff --git a/planetmint/web/views/transactions.py b/planetmint/web/views/transactions.py index eafaeed..6a4c0fb 100644 --- a/planetmint/web/views/transactions.py +++ b/planetmint/web/views/transactions.py @@ -13,7 +13,10 @@ from flask import current_app, request, jsonify from flask_restful import Resource, reqparse from planetmint.transactions.common.transaction_mode_types import BROADCAST_TX_ASYNC -from planetmint.transactions.common.exceptions import SchemaValidationError, ValidationError +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 @@ -32,7 +35,7 @@ class TransactionApi(Resource): Return: A JSON string containing the data about the transaction. """ - pool = current_app.config['bigchain_pool'] + pool = current_app.config["bigchain_pool"] with pool() as planet: tx = planet.get_transaction(tx_id) @@ -46,13 +49,11 @@ class TransactionApi(Resource): class TransactionListApi(Resource): def get(self): parser = reqparse.RequestParser() - parser.add_argument('operation', type=parameters.valid_operation) - parser.add_argument('asset_id', type=parameters.valid_txid, - required=True) - parser.add_argument('last_tx', type=parameters.valid_bool, - required=False) + parser.add_argument("operation", type=parameters.valid_operation) + parser.add_argument("asset_id", type=parameters.valid_txid, required=True) + parser.add_argument("last_tx", type=parameters.valid_bool, required=False) args = parser.parse_args() - with current_app.config['bigchain_pool']() as planet: + with current_app.config["bigchain_pool"]() as planet: txs = planet.get_transactions_filtered(**args) return [tx.to_dict() for tx in txs] @@ -64,12 +65,13 @@ class TransactionListApi(Resource): A ``dict`` containing the data about the transaction. """ parser = reqparse.RequestParser() - parser.add_argument('mode', type=parameters.valid_mode, - default=BROADCAST_TX_ASYNC) + parser.add_argument( + "mode", type=parameters.valid_mode, default=BROADCAST_TX_ASYNC + ) args = parser.parse_args() - mode = str(args['mode']) + mode = str(args["mode"]) - pool = current_app.config['bigchain_pool'] + pool = current_app.config["bigchain_pool"] # `force` will try to format the body of the POST request even if the # `content-type` header is not set to `application/json` @@ -80,13 +82,15 @@ class TransactionListApi(Resource): except SchemaValidationError as e: return make_error( 400, - message='Invalid transaction schema: {}'.format( - e.__cause__.message) + message="Invalid transaction schema: {}".format(e.__cause__.message), + ) + except KeyError as e: + return make_error( + 400, "Invalid transaction ({}): {}".format(type(e).__name__, e) ) except ValidationError as e: return make_error( - 400, - 'Invalid transaction ({}): {}'.format(type(e).__name__, e) + 400, "Invalid transaction ({}): {}".format(type(e).__name__, e) ) with pool() as planet: @@ -94,8 +98,7 @@ class TransactionListApi(Resource): planet.validate_transaction(tx_obj) except ValidationError as e: return make_error( - 400, - 'Invalid transaction ({}): {}'.format(type(e).__name__, e) + 400, "Invalid transaction ({}): {}".format(type(e).__name__, e) ) else: status_code, message = planet.write_transaction(tx_obj, mode) diff --git a/setup.py b/setup.py index 06a4aee..87a9455 100644 --- a/setup.py +++ b/setup.py @@ -14,78 +14,119 @@ import sys from setuptools import setup, find_packages if sys.version_info < (3, 9): - sys.exit('Please use Python version 3.9 or higher.') + sys.exit("Please use Python version 3.9 or higher.") -with open('README.md') as readme_file: +with open("README.md") as readme_file: readme = readme_file.read() # get the version version = {} -with open('planetmint/version.py') as fp: +with open("planetmint/version.py") as fp: exec(fp.read(), version) def check_setuptools_features(): """Check if setuptools is up to date.""" import pkg_resources + try: - list(pkg_resources.parse_requirements('foo~=1.0')) + list(pkg_resources.parse_requirements("foo~=1.0")) except ValueError: - sys.exit('Your Python distribution comes with an incompatible version ' - 'of `setuptools`. Please run:\n' - ' $ pip3 install --upgrade setuptools\n' - 'and then run this command again') + sys.exit( + "Your Python distribution comes with an incompatible version " + "of `setuptools`. Please run:\n" + " $ 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) - ] +docs_require = [ + "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", + "nest-asyncio==1.5.5", + "sphinx-press-theme==0.8.0", + "sphinx-documatt-theme", +] check_setuptools_features() dev_require = [ - 'ipdb', - 'ipython', - 'watchdog', - 'logging_tree', - 'pre-commit', - 'twine', - 'ptvsd' + "ipdb", + "ipython", + "watchdog", + "logging_tree", + "pre-commit", + "twine", + "ptvsd" ] tests_require = [ - 'coverage', - 'pep8', - 'flake8', - 'flake8-quotes==0.8.1', - 'hypothesis>=5.3.0', - 'pytest>=3.0.0', - 'pytest-cov==2.8.1', - 'pytest-mock', - 'pytest-xdist', - 'pytest-flask', - 'pytest-aiohttp', - 'pytest-asyncio', - 'tox', + "coverage", + "pep8", + "flake8", + "flake8-quotes==0.8.1", + "hypothesis>=5.3.0", + "pytest>=3.0.0", + "pytest-cov==2.8.1", + "pytest-mock", + "pytest-xdist", + "pytest-flask", + "pytest-aiohttp", + "pytest-asyncio", + "tox", ] + docs_require install_requires = [ 'chardet==3.0.4', 'aiohttp==3.8.1', 'abci==0.8.3', - 'planetmint-cryptoconditions>=0.9.4', + 'planetmint-cryptoconditions>=0.9.9', 'flask-cors==3.0.10', 'flask-restful==0.3.9', 'flask==2.1.2', 'gunicorn==20.1.0', 'jsonschema==3.2.0', 'logstats==0.3.0', - 'packaging>=20.9', + 'packaging>=20.9', # TODO Consider not installing the db drivers, or putting them in extras. 'pymongo==3.11.4', 'tarantool==0.7.1', @@ -95,56 +136,52 @@ install_requires = [ 'setproctitle==1.2.2', 'werkzeug==2.0.3', 'nest-asyncio==1.5.5', - 'protobuf==3.20' + 'protobuf==3.20.1' ] -if sys.version_info < (3, 9): - install_requires.append('pysha3~=1.0.2') - setup( - name='Planetmint', - version=version['__version__'], - description='Planetmint: The Blockchain Database', + name="Planetmint", + version=version["__version__"], + description="Planetmint: The Blockchain Database", long_description=readme, - long_description_content_type='text/markdown', - url='https://github.com/Planetmint/planetmint/', - author='Planetmint Contributors', - author_email='contact@ipdb.global', - license='AGPLv3', + long_description_content_type="text/markdown", + url="https://github.com/Planetmint/planetmint/", + author="Planetmint Contributors", + author_email="contact@ipdb.global", + license="AGPLv3", zip_safe=False, - python_requires='>=3.9', + python_requires=">=3.9", classifiers=[ - 'Development Status :: 4 - Beta', - 'Intended Audience :: Developers', - 'Topic :: Database', - 'Topic :: Database :: Database Engines/Servers', - 'Topic :: Software Development', - 'Natural Language :: English', - 'License :: OSI Approved :: Apache Software License', - 'Programming Language :: Python :: 3.9', - 'Operating System :: MacOS :: MacOS X', - 'Operating System :: POSIX :: Linux', + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "Topic :: Database", + "Topic :: Database :: Database Engines/Servers", + "Topic :: Software Development", + "Natural Language :: English", + "License :: OSI Approved :: Apache Software License", + "Programming Language :: Python :: 3.9", + "Operating System :: MacOS :: MacOS X", + "Operating System :: POSIX :: Linux", ], - - packages=find_packages(exclude=['tests*']), - - scripts=['pkg/scripts/planetmint-monit-config'], - + packages=find_packages(exclude=["tests*"]), + scripts=["pkg/scripts/planetmint-monit-config"], entry_points={ - 'console_scripts': [ - 'planetmint=planetmint.commands.planetmint:main' - ], + "console_scripts": ["planetmint=planetmint.commands.planetmint:main"], }, install_requires=install_requires, - setup_requires=['pytest-runner'], + setup_requires=["pytest-runner"], tests_require=tests_require, extras_require={ - 'test': tests_require, - 'dev': dev_require + tests_require + docs_require, - 'docs': docs_require, + "test": tests_require, + "dev": dev_require + tests_require + docs_require, + "docs": docs_require, }, package_data={ - 'planetmint.transactions.common.schema': ['v1.0/*.yaml','v2.0/*.yaml','v3.0/*.yaml' ], - 'planetmint.backend.tarantool': ['*.lua'], + "planetmint.transactions.common.schema": [ + "v1.0/*.yaml", + "v2.0/*.yaml", + "v3.0/*.yaml", + ], + "planetmint.backend.tarantool": ["*.lua"], }, ) diff --git a/tests/assets/test_divisible_assets.py b/tests/assets/test_divisible_assets.py index 4c9ebd3..5919025 100644 --- a/tests/assets/test_divisible_assets.py +++ b/tests/assets/test_divisible_assets.py @@ -358,7 +358,6 @@ def test_muiltiple_in_mix_own_multiple_out_single_own_transfer(alice, b, user_pk tx_transfer_signed = tx_transfer.sign([alice.private_key, user_sk]) b.store_bulk_transactions([tx_create_signed]) - print(tx_transfer_signed.to_dict()) assert tx_transfer_signed.validate(b) == tx_transfer_signed assert len(tx_transfer_signed.outputs) == 1 assert tx_transfer_signed.outputs[0].amount == 100 diff --git a/tests/assets/test_zenroom_signing.py b/tests/assets/test_zenroom_signing.py new file mode 100644 index 0000000..0a9dc8e --- /dev/null +++ b/tests/assets/test_zenroom_signing.py @@ -0,0 +1,172 @@ +import pytest +import json +import base58 +from hashlib import sha3_256 +from zenroom import zencode_exec +from cryptoconditions.types.ed25519 import Ed25519Sha256 +from cryptoconditions.types.zenroom import ZenroomSha256 +from planetmint.transactions.common.crypto import generate_key_pair + +CONDITION_SCRIPT = """ + Scenario 'ecdh': create the signature of an object + Given I have the 'keyring' + Given that I have a 'string dictionary' named 'houses' inside 'asset' + When I create the signature of 'houses' + Then print the 'signature'""" + +FULFILL_SCRIPT = """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 'signature' inside 'metadata' + When I verify the 'houses' has a signature in 'signature' by 'Alice' + Then print the string 'ok'""" + +SK_TO_PK = """Scenario 'ecdh': Create the keypair + Given that I am known as '{}' + Given I have the 'keyring' + When I create the ecdh public key + When I create the bitcoin address + Then print my 'ecdh public key' + Then print my 'bitcoin address'""" + +GENERATE_KEYPAIR = """Scenario 'ecdh': Create the keypair + Given that I am known as 'Pippo' + When I create the ecdh key + When I create the bitcoin key + Then print data""" + +ZENROOM_DATA = {"also": "more data"} + +HOUSE_ASSETS = { + "data": { + "houses": [ + { + "name": "Harry", + "team": "Gryffindor", + }, + { + "name": "Draco", + "team": "Slytherin", + }, + ], + } +} + +metadata = {"units": 300, "type": "KG"} + + +def test_zenroom_signing(): + + biolabs = generate_key_pair() + version = "2.0" + + alice = json.loads(zencode_exec(GENERATE_KEYPAIR).output)["keyring"] + bob = json.loads(zencode_exec(GENERATE_KEYPAIR).output)["keyring"] + + zen_public_keys = json.loads( + zencode_exec( + SK_TO_PK.format("Alice"), keys=json.dumps({"keyring": alice}) + ).output + ) + zen_public_keys.update( + json.loads( + zencode_exec( + SK_TO_PK.format("Bob"), keys=json.dumps({"keyring": bob}) + ).output + ) + ) + + zenroomscpt = ZenroomSha256( + script=FULFILL_SCRIPT, data=ZENROOM_DATA, keys=zen_public_keys + ) + print(f"zenroom is: {zenroomscpt.script}") + + # CRYPTO-CONDITIONS: generate the condition uri + condition_uri_zen = zenroomscpt.condition.serialize_uri() + print(f"\nzenroom condition URI: {condition_uri_zen}") + + # CRYPTO-CONDITIONS: construct an unsigned fulfillment dictionary + unsigned_fulfillment_dict_zen = { + "type": zenroomscpt.TYPE_NAME, + "public_key": base58.b58encode(biolabs.public_key).decode(), + } + output = { + "amount": "10", + "condition": { + "details": unsigned_fulfillment_dict_zen, + "uri": condition_uri_zen, + }, + "public_keys": [ + biolabs.public_key, + ], + } + input_ = { + "fulfillment": None, + "fulfills": None, + "owners_before": [ + biolabs.public_key, + ], + } + metadata = { + "result": { + "output": ["ok"] + } + } + token_creation_tx = { + "operation": "CREATE", + "asset": HOUSE_ASSETS, + "metadata": metadata, + "outputs": [ + output, + ], + "inputs": [ + input_, + ], + "version": version, + "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, + ) + + # major workflow: + # we store the fulfill script in the transaction/message (zenroom-sha) + # the condition script is used to fulfill the transaction and create the signature + # + # the server should ick the fulfill script and recreate the zenroom-sha and verify the signature + + message = zenroomscpt.sign(message, CONDITION_SCRIPT, alice) + assert zenroomscpt.validate(message=message) + + message = json.loads(message) + fulfillment_uri_zen = zenroomscpt.serialize_uri() + + message["inputs"][0]["fulfillment"] = fulfillment_uri_zen + tx = message + tx["id"] = None + json_str_tx = json.dumps(tx, sort_keys=True, skipkeys=False, separators=(",", ":")) + # SHA3: hash the serialized id-less transaction to generate the id + shared_creation_txid = sha3_256(json_str_tx.encode()).hexdigest() + message["id"] = shared_creation_txid + + from planetmint.models import Transaction + from planetmint.transactions.common.exceptions import ( + SchemaValidationError, + ValidationError, + ) + + try: + tx_obj = Transaction.from_dict(message) + except SchemaValidationError: + assert () + except ValidationError as e: + print(e) + assert () + + print(f"VALIDATED : {tx_obj}") + assert (tx_obj == False) is False diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index ed2b847..08a53b4 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -16,7 +16,7 @@ from planetmint import ValidatorElection from planetmint.commands.planetmint import run_election_show from planetmint.transactions.types.elections.election import Election from planetmint.lib import Block -from planetmint.migrations.chain_migration_election import ChainMigrationElection +from planetmint.transactions.types.elections.chain_migration_election import ChainMigrationElection from tests.utils import generate_election, generate_validators diff --git a/tests/elections/test_election.py b/tests/elections/test_election.py index e58ec4f..e8197be 100644 --- a/tests/elections/test_election.py +++ b/tests/elections/test_election.py @@ -4,7 +4,7 @@ from tests.utils import generate_election, generate_validators from planetmint.lib import Block from planetmint.transactions.types.elections.election import Election -from planetmint.migrations.chain_migration_election import ChainMigrationElection +from planetmint.transactions.types.elections.chain_migration_election import ChainMigrationElection from planetmint.upsert_validator.validator_election import ValidatorElection @pytest.mark.bdb diff --git a/tests/migrations/test_migration_election.py b/tests/migrations/test_migration_election.py index 3b651ff..8b7cbea 100644 --- a/tests/migrations/test_migration_election.py +++ b/tests/migrations/test_migration_election.py @@ -1,4 +1,4 @@ -from planetmint.migrations.chain_migration_election import ChainMigrationElection +from planetmint.transactions.types.elections.chain_migration_election import ChainMigrationElection def test_valid_migration_election(b_mock, node_key): diff --git a/tests/tendermint/test_core.py b/tests/tendermint/test_core.py index d50a38b..eede330 100644 --- a/tests/tendermint/test_core.py +++ b/tests/tendermint/test_core.py @@ -20,7 +20,7 @@ from planetmint.core import (OkCode, rollback) from planetmint.transactions.types.elections.election import Election from planetmint.lib import Block -from planetmint.migrations.chain_migration_election import ChainMigrationElection +from planetmint.transactions.types.elections.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 diff --git a/tests/test_core.py b/tests/test_core.py index 9336ff5..621b90e 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -18,7 +18,7 @@ from planetmint.core import (OkCode, rollback) from planetmint.transactions.types.elections.election import Election from planetmint.lib import Block -from planetmint.migrations.chain_migration_election import ChainMigrationElection +from planetmint.transactions.types.elections.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 diff --git a/tests/web/test_transactions.py b/tests/web/test_transactions.py index b613c50..dc479a2 100644 --- a/tests/web/test_transactions.py +++ b/tests/web/test_transactions.py @@ -9,6 +9,7 @@ from unittest.mock import Mock, patch import base58 import pytest from cryptoconditions import Ed25519Sha256 + try: from hashlib import sha3_256 except ImportError: @@ -18,9 +19,12 @@ 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) + BROADCAST_TX_COMMIT, + BROADCAST_TX_ASYNC, + BROADCAST_TX_SYNC, +) -TX_ENDPOINT = '/api/v1/transactions/' +TX_ENDPOINT = "/api/v1/transactions/" @pytest.mark.abci @@ -31,10 +35,10 @@ def test_get_transaction_endpoint(client, posted_create_tx): def test_get_transaction_returns_404_if_not_found(client): - res = client.get(TX_ENDPOINT + '123') + res = client.get(TX_ENDPOINT + "123") assert res.status_code == 404 - res = client.get(TX_ENDPOINT + '123/') + res = client.get(TX_ENDPOINT + "123/") assert res.status_code == 404 @@ -49,72 +53,103 @@ def test_post_create_transaction_endpoint(b, client): assert res.status_code == 202 - assert res.json['inputs'][0]['owners_before'][0] == user_pub - assert res.json['outputs'][0]['public_keys'][0] == user_pub + assert res.json["inputs"][0]["owners_before"][0] == user_pub + assert res.json["outputs"][0]["public_keys"][0] == user_pub @pytest.mark.abci -@pytest.mark.parametrize('nested', [False, True]) -@pytest.mark.parametrize('language,expected_status_code', [ - ('danish', 202), ('dutch', 202), ('english', 202), ('finnish', 202), - ('french', 202), ('german', 202), ('hungarian', 202), ('italian', 202), - ('norwegian', 202), ('portuguese', 202), ('romanian', 202), ('none', 202), - ('russian', 202), ('spanish', 202), ('swedish', 202), ('turkish', 202), - ('da', 202), ('nl', 202), ('en', 202), ('fi', 202), ('fr', 202), - ('de', 202), ('hu', 202), ('it', 202), ('nb', 202), ('pt', 202), - ('ro', 202), ('ru', 202), ('es', 202), ('sv', 202), ('tr', 202), - ('any', 400) -]) +@pytest.mark.parametrize("nested", [False, True]) +@pytest.mark.parametrize( + "language,expected_status_code", + [ + ("danish", 202), + ("dutch", 202), + ("english", 202), + ("finnish", 202), + ("french", 202), + ("german", 202), + ("hungarian", 202), + ("italian", 202), + ("norwegian", 202), + ("portuguese", 202), + ("romanian", 202), + ("none", 202), + ("russian", 202), + ("spanish", 202), + ("swedish", 202), + ("turkish", 202), + ("da", 202), + ("nl", 202), + ("en", 202), + ("fi", 202), + ("fr", 202), + ("de", 202), + ("hu", 202), + ("it", 202), + ("nb", 202), + ("pt", 202), + ("ro", 202), + ("ru", 202), + ("es", 202), + ("sv", 202), + ("tr", 202), + ("any", 400), + ], +) @pytest.mark.language -def test_post_create_transaction_with_language(b, client, nested, language, - expected_status_code): +def test_post_create_transaction_with_language( + b, client, nested, language, expected_status_code +): from planetmint.backend.localmongodb.connection import LocalMongoDBConnection if isinstance(b.connection, LocalMongoDBConnection): user_priv, user_pub = crypto.generate_key_pair() - lang_obj = {'language': language} + lang_obj = {"language": language} if nested: - asset = {'root': lang_obj} + asset = {"root": lang_obj} else: asset = lang_obj - tx = Create.generate([user_pub], [([user_pub], 1)], - asset=asset) + 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())) assert res.status_code == expected_status_code if res.status_code == 400: expected_error_message = ( - 'Invalid transaction (ValidationError): MongoDB does not support ' + "Invalid transaction (ValidationError): MongoDB does not support " 'text search for the language "{}". If you do not understand this ' 'error message then please rename key/field "language" to something ' - 'else like "lang".').format(language) - assert res.json['message'] == expected_error_message + 'else like "lang".' + ).format(language) + assert res.json["message"] == expected_error_message @pytest.mark.abci -@pytest.mark.parametrize('field', ['asset', 'metadata']) -@pytest.mark.parametrize('value,err_key,expected_status_code', [ - ({'bad.key': 'v'}, 'bad.key', 400), - ({'$bad.key': 'v'}, '$bad.key', 400), - ({'$badkey': 'v'}, '$badkey', 400), - ({'bad\x00key': 'v'}, 'bad\x00key', 400), - ({'good_key': {'bad.key': 'v'}}, 'bad.key', 400), - ({'good_key': 'v'}, 'good_key', 202) -]) -def test_post_create_transaction_with_invalid_key(b, client, field, value, - err_key, expected_status_code): +@pytest.mark.parametrize("field", ["asset", "metadata"]) +@pytest.mark.parametrize( + "value,err_key,expected_status_code", + [ + ({"bad.key": "v"}, "bad.key", 400), + ({"$bad.key": "v"}, "$bad.key", 400), + ({"$badkey": "v"}, "$badkey", 400), + ({"bad\x00key": "v"}, "bad\x00key", 400), + ({"good_key": {"bad.key": "v"}}, "bad.key", 400), + ({"good_key": "v"}, "good_key", 202), + ], +) +def test_post_create_transaction_with_invalid_key( + b, client, field, value, err_key, expected_status_code +): from planetmint.backend.localmongodb.connection import LocalMongoDBConnection + user_priv, user_pub = crypto.generate_key_pair() if isinstance(b.connection, LocalMongoDBConnection): - if field == 'asset': - tx = Create.generate([user_pub], [([user_pub], 1)], - asset=value) - elif field == 'metadata': - tx = Create.generate([user_pub], [([user_pub], 1)], - metadata=value) + if field == "asset": + tx = Create.generate([user_pub], [([user_pub], 1)], asset=value) + elif field == "metadata": + 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())) @@ -123,60 +158,61 @@ def test_post_create_transaction_with_invalid_key(b, client, field, value, if res.status_code == 400: expected_error_message = ( 'Invalid transaction (ValidationError): Invalid key name "{}" ' - 'in {} object. The key name cannot contain characters ' - '".", "$" or null characters').format(err_key, field) - assert res.json['message'] == expected_error_message + "in {} object. The key name cannot contain characters " + '".", "$" or null characters' + ).format(err_key, field) + assert res.json["message"] == expected_error_message @pytest.mark.abci -@patch('planetmint.web.views.base.logger') +@patch("planetmint.web.views.base.logger") def test_post_create_transaction_with_invalid_id(mock_logger, b, client): from planetmint.transactions.common.exceptions import InvalidHash + user_priv, user_pub = crypto.generate_key_pair() tx = Create.generate([user_pub], [([user_pub], 1)]) tx = tx.sign([user_priv]).to_dict() - tx['id'] = 'abcd' * 16 + tx["id"] = "abcd" * 16 res = client.post(TX_ENDPOINT, data=json.dumps(tx)) expected_status_code = 400 expected_error_message = ( "Invalid transaction ({}): The transaction's id '{}' isn't equal to " "the hash of its body, i.e. it's not valid." - ).format(InvalidHash.__name__, tx['id']) + ).format(InvalidHash.__name__, tx["id"]) assert res.status_code == expected_status_code - assert res.json['message'] == expected_error_message + assert res.json["message"] == expected_error_message assert mock_logger.error.called assert ( - 'HTTP API error: %(status)s - %(method)s:%(path)s - %(message)s' in - mock_logger.error.call_args[0] - ) - assert ( - { - 'message': expected_error_message, 'status': expected_status_code, - 'method': 'POST', 'path': TX_ENDPOINT - } in mock_logger.error.call_args[0] + "HTTP API error: %(status)s - %(method)s:%(path)s - %(message)s" + in mock_logger.error.call_args[0] ) + assert { + "message": expected_error_message, + "status": expected_status_code, + "method": "POST", + "path": TX_ENDPOINT, + } in mock_logger.error.call_args[0] # TODO put back caplog based asserts once possible # assert caplog.records[0].args['status'] == expected_status_code # assert caplog.records[0].args['message'] == expected_error_message @pytest.mark.abci -@patch('planetmint.web.views.base.logger') -def test_post_create_transaction_with_invalid_signature(mock_logger, - b, - client): +@patch("planetmint.web.views.base.logger") +def test_post_create_transaction_with_invalid_signature(mock_logger, b, client): from planetmint.transactions.common.exceptions import InvalidSignature + user_priv, user_pub = crypto.generate_key_pair() tx = Create.generate([user_pub], [([user_pub], 1)]).to_dict() - tx['inputs'][0]['fulfillment'] = 64 * '0' - tx['id'] = sha3_256( + tx["inputs"][0]["fulfillment"] = 64 * "0" + tx["id"] = sha3_256( json.dumps( tx, sort_keys=True, - separators=(',', ':'), + separators=(",", ":"), ensure_ascii=False, ).encode(), ).hexdigest() @@ -184,22 +220,21 @@ def test_post_create_transaction_with_invalid_signature(mock_logger, res = client.post(TX_ENDPOINT, data=json.dumps(tx)) expected_status_code = 400 expected_error_message = ( - 'Invalid transaction ({}): Fulfillment URI ' - 'couldn\'t been parsed' + "Invalid transaction ({}): Fulfillment URI " "couldn't been parsed" ).format(InvalidSignature.__name__) assert res.status_code == expected_status_code - assert res.json['message'] == expected_error_message + assert res.json["message"] == expected_error_message assert mock_logger.error.called assert ( - 'HTTP API error: %(status)s - %(method)s:%(path)s - %(message)s' in - mock_logger.error.call_args[0] - ) - assert ( - { - 'message': expected_error_message, 'status': expected_status_code, - 'method': 'POST', 'path': TX_ENDPOINT - } in mock_logger.error.call_args[0] + "HTTP API error: %(status)s - %(method)s:%(path)s - %(message)s" + in mock_logger.error.call_args[0] ) + assert { + "message": expected_error_message, + "status": expected_status_code, + "method": "POST", + "path": TX_ENDPOINT, + } in mock_logger.error.call_args[0] # TODO put back caplog based asserts once possible # assert caplog.records[0].args['status'] == expected_status_code # assert caplog.records[0].args['message'] == expected_error_message @@ -207,69 +242,81 @@ def test_post_create_transaction_with_invalid_signature(mock_logger, @pytest.mark.abci def test_post_create_transaction_with_invalid_structure(client): - res = client.post(TX_ENDPOINT, data='{}') + res = client.post(TX_ENDPOINT, data="{}") assert res.status_code == 400 @pytest.mark.abci -@patch('planetmint.web.views.base.logger') +@patch("planetmint.web.views.base.logger") def test_post_create_transaction_with_invalid_schema(mock_logger, client): user_priv, user_pub = crypto.generate_key_pair() tx = Create.generate([user_pub], [([user_pub], 1)]).to_dict() - del tx['version'] + del tx["version"] ed25519 = Ed25519Sha256(public_key=base58.b58decode(user_pub)) message = json.dumps( tx, sort_keys=True, - separators=(',', ':'), + separators=(",", ":"), ensure_ascii=False, ).encode() ed25519.sign(message, base58.b58decode(user_priv)) - tx['inputs'][0]['fulfillment'] = ed25519.serialize_uri() - tx['id'] = sha3_256( + tx["inputs"][0]["fulfillment"] = ed25519.serialize_uri() + tx["id"] = sha3_256( json.dumps( tx, sort_keys=True, - separators=(',', ':'), + separators=(",", ":"), ensure_ascii=False, ).encode(), ).hexdigest() res = client.post(TX_ENDPOINT, data=json.dumps(tx)) expected_status_code = 400 expected_error_message = ( - "Invalid transaction schema: 'version' is a required property") + # "Invalid transaction schema: 'version' is a required property" + "Invalid transaction (KeyError): 'version'" + ) assert res.status_code == expected_status_code - assert res.json['message'] == expected_error_message + assert res.json["message"] == expected_error_message assert mock_logger.error.called assert ( - 'HTTP API error: %(status)s - %(method)s:%(path)s - %(message)s' in - mock_logger.error.call_args[0] - ) - assert ( - { - 'message': expected_error_message, 'status': expected_status_code, - 'method': 'POST', 'path': TX_ENDPOINT - } in mock_logger.error.call_args[0] + "HTTP API error: %(status)s - %(method)s:%(path)s - %(message)s" + in mock_logger.error.call_args[0] ) + assert { + "message": expected_error_message, + "status": expected_status_code, + "method": "POST", + "path": TX_ENDPOINT, + } in mock_logger.error.call_args[0] # TODO put back caplog based asserts once possible # assert caplog.records[0].args['status'] == expected_status_code # assert caplog.records[0].args['message'] == expected_error_message @pytest.mark.abci -@pytest.mark.parametrize('exc,msg', ( - ('AmountError', 'Do the math again!'), - ('DoubleSpend', 'Nope! It is gone now!'), - ('InvalidHash', 'Do not smoke that!'), - ('InvalidSignature', 'Falsche Unterschrift!'), - ('ValidationError', 'Create and transfer!'), - ('InputDoesNotExist', 'Hallucinations?'), - ('TransactionOwnerError', 'Not yours!'), - ('ValidationError', '?'), -)) -@patch('planetmint.web.views.base.logger') -def test_post_invalid_transaction(mock_logger, client, exc, msg, monkeypatch,): +@pytest.mark.parametrize( + "exc,msg", + ( + ("AmountError", "Do the math again!"), + ("DoubleSpend", "Nope! It is gone now!"), + ("InvalidHash", "Do not smoke that!"), + ("InvalidSignature", "Falsche Unterschrift!"), + ("ValidationError", "Create and transfer!"), + ("InputDoesNotExist", "Hallucinations?"), + ("TransactionOwnerError", "Not yours!"), + ("ValidationError", "?"), + ), +) +@patch("planetmint.web.views.base.logger") +def test_post_invalid_transaction( + mock_logger, + client, + exc, + msg, + monkeypatch, +): from planetmint.transactions.common import exceptions + exc_cls = getattr(exceptions, exc) def mock_validation(self_, tx): @@ -278,24 +325,24 @@ def test_post_invalid_transaction(mock_logger, client, exc, msg, monkeypatch,): TransactionMock = Mock(validate=mock_validation) monkeypatch.setattr( - 'planetmint.models.Transaction.from_dict', lambda tx: TransactionMock) + "planetmint.models.Transaction.from_dict", lambda tx: TransactionMock + ) res = client.post(TX_ENDPOINT, data=json.dumps({})) expected_status_code = 400 - expected_error_message = 'Invalid transaction ({}): {}'.format(exc, msg) + expected_error_message = "Invalid transaction ({}): {}".format(exc, msg) assert res.status_code == expected_status_code - assert (res.json['message'] == - 'Invalid transaction ({}): {}'.format(exc, msg)) + assert res.json["message"] == "Invalid transaction ({}): {}".format(exc, msg) assert mock_logger.error.called assert ( - 'HTTP API error: %(status)s - %(method)s:%(path)s - %(message)s' in - mock_logger.error.call_args[0] - ) - assert ( - { - 'message': expected_error_message, 'status': expected_status_code, - 'method': 'POST', 'path': TX_ENDPOINT - } in mock_logger.error.call_args[0] + "HTTP API error: %(status)s - %(method)s:%(path)s - %(message)s" + in mock_logger.error.call_args[0] ) + assert { + "message": expected_error_message, + "status": expected_status_code, + "method": "POST", + "path": TX_ENDPOINT, + } in mock_logger.error.call_args[0] # TODO put back caplog based asserts once possible # assert caplog.records[2].args['status'] == expected_status_code # assert caplog.records[2].args['message'] == expected_error_message @@ -304,34 +351,37 @@ 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): - transfer_tx = Transfer.generate(posted_create_tx.to_inputs(), - [([user_pk], 1)], - asset_id=posted_create_tx.id) + 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]) res = client.post(TX_ENDPOINT, data=json.dumps(transfer_tx.to_dict())) assert res.status_code == 202 - assert res.json['inputs'][0]['owners_before'][0] == user_pk - assert res.json['outputs'][0]['public_keys'][0] == user_pk + assert res.json["inputs"][0]["owners_before"][0] == user_pk + assert res.json["outputs"][0]["public_keys"][0] == user_pk @pytest.mark.abci -def test_post_invalid_transfer_transaction_returns_400(client, user_pk, posted_create_tx): +def test_post_invalid_transfer_transaction_returns_400( + client, user_pk, posted_create_tx +): from planetmint.transactions.common.exceptions import InvalidSignature - transfer_tx = Transfer.generate(posted_create_tx.to_inputs(), - [([user_pk], 1)], - asset_id=posted_create_tx.id) + transfer_tx = Transfer.generate( + posted_create_tx.to_inputs(), [([user_pk], 1)], asset_id=posted_create_tx.id + ) transfer_tx._hash() res = client.post(TX_ENDPOINT, data=json.dumps(transfer_tx.to_dict())) expected_status_code = 400 - expected_error_message = 'Invalid transaction ({}): {}'.format( - InvalidSignature.__name__, 'Transaction signature is invalid.') + expected_error_message = "Invalid transaction ({}): {}".format( + InvalidSignature.__name__, "Transaction signature is invalid." + ) assert res.status_code == expected_status_code - assert res.json['message'] == expected_error_message + assert res.json["message"] == expected_error_message @pytest.mark.abci @@ -340,22 +390,27 @@ def test_post_wrong_asset_division_transfer_returns_400(b, client, user_pk): priv_key, pub_key = crypto.generate_key_pair() - 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())) + 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 = 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())) - expected_error_message = \ - f'Invalid transaction ({AmountError.__name__}): ' + \ - 'The amount used in the inputs `10` needs to be same as the amount used in the outputs `20`' + transfer_tx = Transfer.generate( + create_tx.to_inputs(), [([pub_key], 20)], asset_id=create_tx.id # 20 > 10 + ).sign([priv_key]) + res = client.post( + TX_ENDPOINT + "?mode=commit", data=json.dumps(transfer_tx.to_dict()) + ) + expected_error_message = ( + f"Invalid transaction ({AmountError.__name__}): " + + "The amount used in the inputs `10` needs to be same as the amount used in the outputs `20`" + ) assert res.status_code == 400 - assert res.json['message'] == expected_error_message + assert res.json["message"] == expected_error_message def test_transactions_get_list_good(client): @@ -363,87 +418,96 @@ def test_transactions_get_list_good(client): def get_txs_patched(conn, **args): """Patch `get_transactions_filtered` so that rather than return an array - of transactions it returns an array of shims with a to_dict() method - that reports one of the arguments passed to `get_transactions_filtered`. - """ - return [type('', (), {'to_dict': partial(lambda a: a, arg)}) - for arg in sorted(args.items())] - - asset_id = '1' * 64 - - with patch('planetmint.Planetmint.get_transactions_filtered', get_txs_patched): - url = TX_ENDPOINT + '?asset_id=' + asset_id - assert client.get(url).json == [ - ['asset_id', asset_id], - ['last_tx', None], - ['operation', None] + of transactions it returns an array of shims with a to_dict() method + that reports one of the arguments passed to `get_transactions_filtered`. + """ + return [ + type("", (), {"to_dict": partial(lambda a: a, arg)}) + for arg in sorted(args.items()) ] - url = TX_ENDPOINT + '?asset_id=' + asset_id + '&operation=CREATE' + + asset_id = "1" * 64 + + with patch("planetmint.Planetmint.get_transactions_filtered", get_txs_patched): + url = TX_ENDPOINT + "?asset_id=" + asset_id assert client.get(url).json == [ - ['asset_id', asset_id], - ['last_tx', None], - ['operation', 'CREATE'] + ["asset_id", asset_id], + ["last_tx", None], + ["operation", None], ] - url = TX_ENDPOINT + '?asset_id=' + asset_id + '&last_tx=true' + url = TX_ENDPOINT + "?asset_id=" + asset_id + "&operation=CREATE" assert client.get(url).json == [ - ['asset_id', asset_id], - ['last_tx', True], - ['operation', None] + ["asset_id", asset_id], + ["last_tx", None], + ["operation", "CREATE"], + ] + url = TX_ENDPOINT + "?asset_id=" + asset_id + "&last_tx=true" + assert client.get(url).json == [ + ["asset_id", asset_id], + ["last_tx", True], + ["operation", None], ] def test_transactions_get_list_bad(client): def should_not_be_called(): assert False - with patch('planetmint.Planetmint.get_transactions_filtered', - lambda *_, **__: should_not_be_called()): + + with patch( + "planetmint.Planetmint.get_transactions_filtered", + lambda *_, **__: should_not_be_called(), + ): # Test asset id validated - url = TX_ENDPOINT + '?asset_id=' + '1' * 63 + url = TX_ENDPOINT + "?asset_id=" + "1" * 63 assert client.get(url).status_code == 400 # Test operation validated - url = TX_ENDPOINT + '?asset_id=' + '1' * 64 + '&operation=CEATE' + url = TX_ENDPOINT + "?asset_id=" + "1" * 64 + "&operation=CEATE" assert client.get(url).status_code == 400 # Test asset ID required - url = TX_ENDPOINT + '?operation=CREATE' + url = TX_ENDPOINT + "?operation=CREATE" assert client.get(url).status_code == 400 -@patch('requests.post') -@pytest.mark.parametrize('mode', [ - ('', BROADCAST_TX_ASYNC), - ('?mode=async', BROADCAST_TX_ASYNC), - ('?mode=sync', BROADCAST_TX_SYNC), - ('?mode=commit', BROADCAST_TX_COMMIT), -]) +@patch("requests.post") +@pytest.mark.parametrize( + "mode", + [ + ("", BROADCAST_TX_ASYNC), + ("?mode=async", BROADCAST_TX_ASYNC), + ("?mode=sync", BROADCAST_TX_SYNC), + ("?mode=commit", BROADCAST_TX_COMMIT), + ], +) def test_post_transaction_valid_modes(mock_post, client, mode): from planetmint.transactions.common.crypto import generate_key_pair def _mock_post(*args, **kwargs): - return Mock(json=Mock(return_value={'result': {'code': 0}})) + return Mock(json=Mock(return_value={"result": {"code": 0}})) mock_post.side_effect = _mock_post alice = generate_key_pair() - tx = Create.generate([alice.public_key], - [([alice.public_key], 1)], - asset=None) \ - .sign([alice.private_key]) + tx = Create.generate( + [alice.public_key], [([alice.public_key], 1)], asset=None + ).sign([alice.private_key]) mode_endpoint = TX_ENDPOINT + mode[0] client.post(mode_endpoint, data=json.dumps(tx.to_dict())) args, kwargs = mock_post.call_args - assert mode[1] == kwargs['json']['method'] + assert mode[1] == kwargs["json"]["method"] @pytest.mark.abci def test_post_transaction_invalid_mode(client): from planetmint.transactions.common.crypto import generate_key_pair + alice = generate_key_pair() - tx = Create.generate([alice.public_key], - [([alice.public_key], 1)], - asset=None) \ - .sign([alice.private_key]) - mode_endpoint = TX_ENDPOINT + '?mode=nope' + tx = Create.generate( + [alice.public_key], [([alice.public_key], 1)], asset=None + ).sign([alice.private_key]) + mode_endpoint = TX_ENDPOINT + "?mode=nope" response = client.post(mode_endpoint, data=json.dumps(tx.to_dict())) - assert '400 BAD REQUEST' in response.status - assert 'Mode must be "async", "sync" or "commit"' ==\ - json.loads(response.data.decode('utf8'))['message']['mode'] + assert "400 BAD REQUEST" in response.status + assert ( + 'Mode must be "async", "sync" or "commit"' + == json.loads(response.data.decode("utf8"))["message"]["mode"] + ) diff --git a/tox.ini b/tox.ini index 0cc9c26..86badc5 100644 --- a/tox.ini +++ b/tox.ini @@ -25,7 +25,7 @@ extras = None commands = flake8 planetmint tests [flake8] -ignore = E126 E127 W504 E302 E126 E305 +ignore = E126 E127 W504 E302 E126 E305 W503 E712 F401 [testenv:docsroot] basepython = {[base]basepython}