From 0142e98dbab4db00adab45d589539c8459944940 Mon Sep 17 00:00:00 2001 From: vrde Date: Fri, 17 Aug 2018 14:08:49 +0200 Subject: [PATCH 1/5] Problem: Rapid JSON is outdated and slow (#2470) Solution: Use the last version of Rapid JSON. --- bigchaindb/common/schema/__init__.py | 5 ++--- setup.py | 3 +-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/bigchaindb/common/schema/__init__.py b/bigchaindb/common/schema/__init__.py index f29e9b44..914e5196 100644 --- a/bigchaindb/common/schema/__init__.py +++ b/bigchaindb/common/schema/__init__.py @@ -9,7 +9,6 @@ import logging import jsonschema import yaml import rapidjson -import rapidjson_schema from bigchaindb.common.exceptions import SchemaValidationError @@ -22,7 +21,7 @@ def _load_schema(name, path=__file__): path = os.path.join(os.path.dirname(path), name + '.yaml') with open(path) as handle: schema = yaml.safe_load(handle) - fast_schema = rapidjson_schema.loads(rapidjson.dumps(schema)) + fast_schema = rapidjson.Validator(rapidjson.dumps(schema)) return path, (schema, fast_schema) @@ -57,7 +56,7 @@ def _validate_schema(schema, body): # a helpful error message. try: - schema[1].validate(rapidjson.dumps(body)) + schema[1](rapidjson.dumps(body)) except ValueError as exc: try: jsonschema.validate(body, schema[0]) diff --git a/setup.py b/setup.py index eb781aee..dcecca51 100644 --- a/setup.py +++ b/setup.py @@ -80,7 +80,7 @@ install_requires = [ 'pymongo~=3.6', 'pysha3~=1.0.2', 'cryptoconditions~=0.6.0.dev', - 'python-rapidjson==0.0.11', + 'python-rapidjson~=0.6.0', 'logstats~=0.2.1', 'flask>=0.10.1', 'flask-cors~=3.0.0', @@ -90,7 +90,6 @@ install_requires = [ 'jsonschema~=2.5.1', 'pyyaml~=3.12', 'aiohttp~=3.0', - 'python-rapidjson-schema==0.1.1', 'bigchaindb-abci==0.5.1', 'setproctitle~=1.1.0', ] From 423820bcda2592457b6635a3ac1b3189089f8975 Mon Sep 17 00:00:00 2001 From: Troy McConaghy Date: Fri, 17 Aug 2018 14:19:58 +0200 Subject: [PATCH 2/5] Problem: simple-network-setup page getting long (#2464) Solution: Start refactoring it into a new section --- docs/server/source/index.rst | 2 +- docs/server/source/network-operations/index.rst | 12 ++++++++++++ .../network-setup.md} | 0 3 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 docs/server/source/network-operations/index.rst rename docs/server/source/{simple-network-setup.md => network-operations/network-setup.md} (100%) diff --git a/docs/server/source/index.rst b/docs/server/source/index.rst index a47882ae..fc9fbf81 100644 --- a/docs/server/source/index.rst +++ b/docs/server/source/index.rst @@ -12,7 +12,7 @@ BigchainDB Server Documentation ← Back to All BigchainDB Docs introduction quickstart - simple-network-setup + network-operations/index production-nodes/index clusters dev-and-test/index diff --git a/docs/server/source/network-operations/index.rst b/docs/server/source/network-operations/index.rst new file mode 100644 index 00000000..938bbff4 --- /dev/null +++ b/docs/server/source/network-operations/index.rst @@ -0,0 +1,12 @@ + +.. Copyright BigchainDB GmbH and BigchainDB contributors + SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) + Code is Apache-2.0 and docs are CC-BY-4.0 + +How to Set Up and Run a BigchainDB Network +========================================== + +.. toctree:: + :maxdepth: 1 + + network-setup diff --git a/docs/server/source/simple-network-setup.md b/docs/server/source/network-operations/network-setup.md similarity index 100% rename from docs/server/source/simple-network-setup.md rename to docs/server/source/network-operations/network-setup.md From bd49a3804fd8cadf5669b1b998e5ced8b1b4ed91 Mon Sep 17 00:00:00 2001 From: Troy McConaghy Date: Fri, 17 Aug 2018 16:04:58 +0200 Subject: [PATCH 3/5] Problem: Docs say "BigchainDB cluster" but IRL we say "BigchainDB network" (#2471) Solution: Edit the docs to say "BigchainDB network" instead --- docs/root/source/assets.rst | 2 +- docs/root/source/decentralized.md | 8 +++---- docs/root/source/index.rst | 2 +- docs/root/source/terminology.md | 10 ++++----- docs/server/source/appendices/aws-setup.md | 13 +++++------ docs/server/source/index.rst | 2 +- docs/server/source/introduction.md | 12 +++++----- .../ca-installation.rst | 4 ++-- .../client-tls-certificate.rst | 6 ++--- .../source/k8s-deployment-template/index.rst | 2 +- .../k8s-deployment-template/log-analytics.rst | 2 +- .../node-config-map-and-secrets.rst | 4 ++-- .../revoke-tls-certificate.rst | 6 ++--- .../server-tls-certificate.rst | 4 ++-- .../k8s-deployment-template/workflow.rst | 10 ++++----- .../source/{clusters.md => networks.md} | 22 +++++++++---------- .../production-nodes/node-assumptions.md | 5 ++--- .../production-nodes/node-components.md | 2 +- 18 files changed, 56 insertions(+), 60 deletions(-) rename docs/server/source/{clusters.md => networks.md} (74%) diff --git a/docs/root/source/assets.rst b/docs/root/source/assets.rst index 2726d724..fdf5c945 100644 --- a/docs/root/source/assets.rst +++ b/docs/root/source/assets.rst @@ -8,7 +8,7 @@ How BigchainDB is Good for Asset Registrations & Transfers BigchainDB can store data of any kind (within reason), but it's designed to be particularly good for storing asset registrations and transfers: -* The fundamental thing that one sends to a BigchainDB cluster, to be checked and stored (if valid), is a *transaction*, and there are two kinds: CREATE transactions and TRANSFER transactions. +* The fundamental thing that one sends to a BigchainDB network, to be checked and stored (if valid), is a *transaction*, and there are two kinds: CREATE transactions and TRANSFER transactions. * A CREATE transaction can be use to register any kind of asset (divisible or indivisible), along with arbitrary metadata. * An asset can have zero, one, or several owners. * The owners of an asset can specify (crypto-)conditions which must be satisfied by anyone wishing transfer the asset to new owners. For example, a condition might be that at least 3 of the 5 current owners must cryptographically sign a TRANSFER transaction. diff --git a/docs/root/source/decentralized.md b/docs/root/source/decentralized.md index 7a72bea3..4486839d 100644 --- a/docs/root/source/decentralized.md +++ b/docs/root/source/decentralized.md @@ -8,9 +8,9 @@ Code is Apache-2.0 and docs are CC-BY-4.0 Decentralization means that no one owns or controls everything, and there is no single point of failure. -Ideally, each node in a BigchainDB cluster is owned and controlled by a different person or organization. Even if the cluster lives within one organization, it's still preferable to have each node controlled by a different person or subdivision. +Ideally, each node in a BigchainDB network is owned and controlled by a different person or organization. Even if the network lives within one organization, it's still preferable to have each node controlled by a different person or subdivision. -We use the phrase "BigchainDB consortium" (or just "consortium") to refer to the set of people and/or organizations who run the nodes of a BigchainDB cluster. A consortium requires some form of governance to make decisions such as membership and policies. The exact details of the governance process are determined by each consortium, but it can be very decentralized. +We use the phrase "BigchainDB consortium" (or just "consortium") to refer to the set of people and/or organizations who run the nodes of a BigchainDB network. A consortium requires some form of governance to make decisions such as membership and policies. The exact details of the governance process are determined by each consortium, but it can be very decentralized. If sharding is turned on (i.e. if the number of shards is larger than one), then the actual data is decentralized in that no one node stores all the data. @@ -18,8 +18,8 @@ Every node has its own locally-stored list of the public keys of other consortiu A consortium can increase its decentralization (and its resilience) by increasing its jurisdictional diversity, geographic diversity, and other kinds of diversity. This idea is expanded upon in [the section on node diversity](diversity.html). -There’s no node that has a long-term special position in the cluster. All nodes run the same software and perform the same duties. +There’s no node that has a long-term special position in the BigchainDB network. All nodes run the same software and perform the same duties. -If someone has (or gets) admin access to a node, they can mess with that node (e.g. change or delete data stored on that node), but those changes should remain isolated to that node. The BigchainDB cluster can only be compromised if more than one third of the nodes get compromised. See the [Tendermint documentation](https://tendermint.readthedocs.io/projects/tools/en/master/introduction.html) for more details. +If someone has (or gets) admin access to a node, they can mess with that node (e.g. change or delete data stored on that node), but those changes should remain isolated to that node. The BigchainDB network can only be compromised if more than one third of the nodes get compromised. See the [Tendermint documentation](https://tendermint.readthedocs.io/projects/tools/en/master/introduction.html) for more details. It’s worth noting that not even the admin or superuser of a node can transfer assets. The only way to create a valid transfer transaction is to fulfill the current crypto-conditions on the asset, and the admin/superuser can’t do that because the admin user doesn’t have the necessary information (e.g. private keys). diff --git a/docs/root/source/index.rst b/docs/root/source/index.rst index b844db4f..4c9c32be 100644 --- a/docs/root/source/index.rst +++ b/docs/root/source/index.rst @@ -13,7 +13,7 @@ including `decentralization `_, `immutability `_ and `native support for assets `_. -At a high level, one can communicate with a BigchainDB cluster (set of nodes) using the BigchainDB HTTP API, or a wrapper for that API, such as the BigchainDB Python Driver. Each BigchainDB node runs BigchainDB Server and various other software. The `terminology page `_ explains some of those terms in more detail. +At a high level, one can communicate with a BigchainDB network (set of nodes) using the BigchainDB HTTP API, or a wrapper for that API, such as the BigchainDB Python Driver. Each BigchainDB node runs BigchainDB Server and various other software. The `terminology page `_ explains some of those terms in more detail. .. raw:: html diff --git a/docs/root/source/terminology.md b/docs/root/source/terminology.md index f0593c7c..5ed67c65 100644 --- a/docs/root/source/terminology.md +++ b/docs/root/source/terminology.md @@ -12,14 +12,14 @@ There is some specialized terminology associated with BigchainDB. To get started A **BigchainDB node** is a machine (or logical machine) running [BigchainDB Server](https://docs.bigchaindb.com/projects/server/en/latest/introduction.html) and related software. Each node is controlled by one person or organization. -## BigchainDB Cluster +## BigchainDB Network -A set of BigchainDB nodes can connect to each other to form a **BigchainDB cluster**. Each node in the cluster runs the same software. A cluster may have additional machines to do things such as cluster monitoring. +A set of BigchainDB nodes can connect to each other to form a **BigchainDB network**. Each node in the network runs the same software. A BigchainDB network may have additional machines to do things such as monitoring. ## BigchainDB Consortium -The people and organizations that run the nodes in a cluster belong to a **BigchainDB consortium** (i.e. another organization). A consortium must have some sort of governance structure to make decisions. If a cluster is run by a single company, then the "consortium" is just that company. +The people and organizations that run the nodes in a BigchainDB network belong to a **BigchainDB consortium** (i.e. another organization). A consortium must have some sort of governance structure to make decisions. If a BigchainDB network is run by a single company, then the "consortium" is just that company. -**What's the Difference Between a Cluster and a Consortium?** +**What's the Difference Between a BigchainDB Network and a Consortium?** -A cluster is just a bunch of connected nodes. A consortium is an organization which has a cluster, and where each node in the cluster has a different operator. \ No newline at end of file +A BigchaindB network is just a bunch of connected nodes. A consortium is an organization which has a BigchainDB network, and where each node in that network has a different operator. \ No newline at end of file diff --git a/docs/server/source/appendices/aws-setup.md b/docs/server/source/appendices/aws-setup.md index ff05be17..c130b392 100644 --- a/docs/server/source/appendices/aws-setup.md +++ b/docs/server/source/appendices/aws-setup.md @@ -4,36 +4,36 @@ SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) Code is Apache-2.0 and docs are CC-BY-4.0 ---> -# Basic AWS Setup +# Basic AWS Setup Before you can deploy anything on AWS, you must do a few things. - ## Get an AWS Account If you don't already have an AWS account, you can [sign up for one for free at aws.amazon.com](https://aws.amazon.com/). - ## Install the AWS Command-Line Interface To install the AWS Command-Line Interface (CLI), just do: + ```text pip install awscli ``` - ## Create an AWS Access Key The next thing you'll need is AWS access keys (access key ID and secret access key). If you don't have those, see [the AWS documentation about access keys](https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html#access-keys-and-secret-access-keys). -You should also pick a default AWS region name (e.g. `eu-central-1`). That's where your cluster will run. The AWS documentation has [a list of them](http://docs.aws.amazon.com/general/latest/gr/rande.html#ec2_region). +You should also pick a default AWS region name (e.g. `eu-central-1`). The AWS documentation has [a list of them](http://docs.aws.amazon.com/general/latest/gr/rande.html#ec2_region). Once you've got your AWS access key, and you've picked a default AWS region name, go to a terminal session and enter: + ```text aws configure ``` and answer the four questions. For example: + ```text AWS Access Key ID [None]: AKIAIOSFODNN7EXAMPLE AWS Secret Access Key [None]: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY @@ -43,17 +43,16 @@ Default output format [None]: [Press Enter] This writes two files: `~/.aws/credentials` and `~/.aws/config`. AWS tools and packages look for those files. - ## Generate an RSA Key Pair for SSH Eventually, you'll have one or more instances (virtual machines) running on AWS and you'll want to SSH to them. To do that, you need a public/private key pair. The public key will be sent to AWS, and you can tell AWS to put it in any instances you provision there. You'll keep the private key on your local workstation. See the [page about how to generate a key pair for SSH](generate-key-pair-for-ssh.html). - ## Send the Public Key to AWS To send the public key to AWS, use the AWS Command-Line Interface: + ```text aws ec2 import-key-pair \ --key-name "" \ diff --git a/docs/server/source/index.rst b/docs/server/source/index.rst index fc9fbf81..e21fdaad 100644 --- a/docs/server/source/index.rst +++ b/docs/server/source/index.rst @@ -14,7 +14,7 @@ BigchainDB Server Documentation quickstart network-operations/index production-nodes/index - clusters + networks dev-and-test/index server-reference/index http-client-server-api diff --git a/docs/server/source/introduction.md b/docs/server/source/introduction.md index 327b2c80..bd15d61e 100644 --- a/docs/server/source/introduction.md +++ b/docs/server/source/introduction.md @@ -14,17 +14,15 @@ Note that there are a few kinds of nodes: - A **dev/test node** is a node created by a developer working on BigchainDB Server, e.g. for testing new or changed code. A dev/test node is typically run on the developer's local machine. -- A **bare-bones node** is a node deployed in the cloud, either as part of a testing cluster or as a starting point before upgrading the node to be production-ready. - -- A **production node** is a node that is part of a consortium's BigchainDB cluster. A production node has the most components and requirements. +- A **bare-bones node** is a node deployed in the cloud, either as part of a testing network or as a starting point before upgrading the node to be production-ready. +- A **production node** is a node that is part of a consortium's BigchainDB network. A production node has the most components and requirements. ## Setup Instructions for Various Cases -* [Quickstart](quickstart.html) -* [Set up a local BigchainDB node for development, experimenting and testing](https://docs.bigchaindb.com/projects/contributing/en/latest/dev-setup-coding-and-contribution-process/index.html) -* [Set up and run a BigchainDB cluster](clusters.html) - +- [Quickstart](quickstart.html) +- [Set up a local BigchainDB node for development, experimenting and testing](https://docs.bigchaindb.com/projects/contributing/en/latest/dev-setup-coding-and-contribution-process/index.html) +- [Set up and run a BigchainDB network](network-operations/index.html) ## Can I Help? diff --git a/docs/server/source/k8s-deployment-template/ca-installation.rst b/docs/server/source/k8s-deployment-template/ca-installation.rst index 51f9259b..e9622e9d 100644 --- a/docs/server/source/k8s-deployment-template/ca-installation.rst +++ b/docs/server/source/k8s-deployment-template/ca-installation.rst @@ -9,8 +9,8 @@ How to Set Up a Self-Signed Certificate Authority ================================================= This page enumerates the steps *we* use to set up a self-signed certificate authority (CA). -This is something that only needs to be done once per cluster, -by the organization managing the cluster, i.e. the CA is for the whole cluster. +This is something that only needs to be done once per BigchainDB network, +by the organization managing the network, i.e. the CA is for the whole network. We use Easy-RSA. diff --git a/docs/server/source/k8s-deployment-template/client-tls-certificate.rst b/docs/server/source/k8s-deployment-template/client-tls-certificate.rst index b30f0df1..5826dc4e 100644 --- a/docs/server/source/k8s-deployment-template/client-tls-certificate.rst +++ b/docs/server/source/k8s-deployment-template/client-tls-certificate.rst @@ -9,7 +9,7 @@ How to Generate a Client Certificate for MongoDB ================================================ This page enumerates the steps *we* use to generate a client certificate to be -used by clients who want to connect to a TLS-secured MongoDB cluster. +used by clients who want to connect to a TLS-secured MongoDB database. We use Easy-RSA. @@ -42,7 +42,7 @@ and using: You should change the Common Name (e.g. ``bdb-instance-0``) to a value that reflects what the -client certificate is being used for, e.g. ``mdb-mon-instance-3`` or ``mdb-bak-instance-4``. (The final integer is specific to your BigchainDB node in the BigchainDB cluster.) +client certificate is being used for, e.g. ``mdb-mon-instance-3`` or ``mdb-bak-instance-4``. (The final integer is specific to your BigchainDB node in the BigchainDB network.) You will be prompted to enter the Distinguished Name (DN) information for this certificate. For each field, you can accept the default value [in brackets] by pressing Enter. @@ -66,7 +66,7 @@ Step 3: Get the Client Certificate Signed The CSR file created in the previous step should be located in ``pki/reqs/bdb-instance-0.req`` (or whatever Common Name you used in the ``gen-req`` command above). -You need to send it to the organization managing the cluster +You need to send it to the organization managing the BigchainDB network so that they can use their CA to sign the request. (The managing organization should already have a self-signed CA.) diff --git a/docs/server/source/k8s-deployment-template/index.rst b/docs/server/source/k8s-deployment-template/index.rst index 1905767e..abc1de07 100644 --- a/docs/server/source/k8s-deployment-template/index.rst +++ b/docs/server/source/k8s-deployment-template/index.rst @@ -17,7 +17,7 @@ Kubernetes Deployment Template and your organization has people who know Kubernetes, then this Kubernetes deployment template might be helpful. -This section outlines a way to deploy a BigchainDB node (or BigchainDB cluster) +This section outlines a way to deploy a BigchainDB node (or BigchainDB network) on Microsoft Azure using Kubernetes. You may choose to use it as a template or reference for your own deployment, but *we make no claim that it is suitable for your purposes*. diff --git a/docs/server/source/k8s-deployment-template/log-analytics.rst b/docs/server/source/k8s-deployment-template/log-analytics.rst index 9235718b..2c7c3271 100644 --- a/docs/server/source/k8s-deployment-template/log-analytics.rst +++ b/docs/server/source/k8s-deployment-template/log-analytics.rst @@ -170,7 +170,7 @@ Until we figure out a way to obtain the *workspace key* via the command line, you can get it via the OMS Portal. To get to the OMS Portal, go to the Azure Portal and click on: -Resource Groups > (Your k8s cluster's resource group) > Log analytics (OMS) > (Name of the only item listed) > OMS Workspace > OMS Portal +Resource Groups > (Your Kubernetes cluster's resource group) > Log analytics (OMS) > (Name of the only item listed) > OMS Workspace > OMS Portal (Let us know if you find a faster way.) Then see `Microsoft's instructions to obtain your workspace ID and key diff --git a/docs/server/source/k8s-deployment-template/node-config-map-and-secrets.rst b/docs/server/source/k8s-deployment-template/node-config-map-and-secrets.rst index d5c28b0f..cbd18008 100644 --- a/docs/server/source/k8s-deployment-template/node-config-map-and-secrets.rst +++ b/docs/server/source/k8s-deployment-template/node-config-map-and-secrets.rst @@ -74,7 +74,7 @@ This user is created on the *admin* database with the authorization to create ot vars.BDB_PERSISTENT_PEERS, BDB_VALIDATORS, BDB_VALIDATORS_POWERS, BDB_GENESIS_TIME and BDB_CHAIN_ID ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -These parameters are shared across the cluster. More information about the generation +These parameters are shared across the BigchainDB network. More information about the generation of these parameters can be found at :ref:`generate-the-blockchain-id-and-genesis-time`. @@ -82,7 +82,7 @@ vars.NODE_DNS_SERVER ^^^^^^^^^^^^^^^^^^^^ IP of Kubernetes service(kube-dns), can be retrieved using using CLI(kubectl) or k8s dashboard. This parameter is used by the Nginx gateway instance -to resolve the hostnames of all the services running in the k8s cluster. +to resolve the hostnames of all the services running in the Kubernetes cluster. .. code:: diff --git a/docs/server/source/k8s-deployment-template/revoke-tls-certificate.rst b/docs/server/source/k8s-deployment-template/revoke-tls-certificate.rst index db7c1c50..560b39f9 100644 --- a/docs/server/source/k8s-deployment-template/revoke-tls-certificate.rst +++ b/docs/server/source/k8s-deployment-template/revoke-tls-certificate.rst @@ -7,9 +7,9 @@ How to Revoke an SSL/TLS Certificate ==================================== This page enumerates the steps *we* take to revoke a self-signed SSL/TLS -certificate in a cluster. +certificate in a BigchainDB network. It can only be done by someone with access to the self-signed CA -associated with the cluster's managing organization. +associated with the network's managing organization. Step 1: Revoke a Certificate ---------------------------- @@ -45,4 +45,4 @@ Generate a new CRL for your infrastructure using: The generated ``crl.pem`` file needs to be uploaded to your infrastructure to prevent the revoked certificate from being used again. -In particlar, the generated ``crl.pem`` file should be sent to all BigchainDB node operators in your BigchainDB cluster, so that they can update it in their MongoDB instance and their BigchainDB Server instance. +In particlar, the generated ``crl.pem`` file should be sent to all BigchainDB node operators in your BigchainDB network, so that they can update it in their MongoDB instance and their BigchainDB Server instance. diff --git a/docs/server/source/k8s-deployment-template/server-tls-certificate.rst b/docs/server/source/k8s-deployment-template/server-tls-certificate.rst index e6f2d757..bab23ed7 100644 --- a/docs/server/source/k8s-deployment-template/server-tls-certificate.rst +++ b/docs/server/source/k8s-deployment-template/server-tls-certificate.rst @@ -47,7 +47,7 @@ and using something like: ./easyrsa --req-cn=mdb-instance-0 --subject-alt-name=DNS:localhost,DNS:mdb-instance-0 gen-req mdb-instance-0 nopass -You should replace the Common Name (``mdb-instance-0`` above) with the correct name for *your* MongoDB instance in the cluster, e.g. ``mdb-instance-5`` or ``mdb-instance-12``. (This name is decided by the organization managing the cluster.) +You should replace the Common Name (``mdb-instance-0`` above) with the correct name for *your* MongoDB instance in the network, e.g. ``mdb-instance-5`` or ``mdb-instance-12``. (This name is decided by the organization managing the network.) You will be prompted to enter the Distinguished Name (DN) information for this certificate. For each field, you can accept the default value [in brackets] by pressing Enter. @@ -68,7 +68,7 @@ Step 3: Get the Server Certificate Signed The CSR file created in the last step should be located in ``pki/reqs/mdb-instance-0.req`` (where the integer ``0`` may be different for you). -You need to send it to the organization managing the cluster +You need to send it to the organization managing the BigchainDB network so that they can use their CA to sign the request. (The managing organization should already have a self-signed CA.) diff --git a/docs/server/source/k8s-deployment-template/workflow.rst b/docs/server/source/k8s-deployment-template/workflow.rst index 94e76a72..028d539f 100644 --- a/docs/server/source/k8s-deployment-template/workflow.rst +++ b/docs/server/source/k8s-deployment-template/workflow.rst @@ -20,7 +20,7 @@ Overview then this Kubernetes deployment template might be helpful. This page summarizes some steps to go through -to set up a BigchainDB cluster. +to set up a BigchainDB network. You can modify them to suit your needs. .. _generate-the-blockchain-id-and-genesis-time: @@ -30,7 +30,7 @@ Generate All Shared BigchainDB Setup Parameters There are some shared BigchainDB setup paramters that every node operator in the consortium shares -because they are properties of the Tendermint cluster. +because they are properties of the Tendermint network. They look like this: .. code:: @@ -46,7 +46,7 @@ Those paramters only have to be generated once, by one member of the consortium. That person will then share the results (Tendermint setup parameters) with all the node operators. -The above example parameters are for a cluster of 4 initial (seed) nodes. +The above example parameters are for a network of 4 initial (seed) nodes. Note how ``BDB_PERSISTENT_PEERS``, ``BDB_VALIDATORS`` and ``BDB_VALIDATOR_POWERS`` are lists with 4 items each. **If your consortium has a different number of initial nodes, @@ -119,7 +119,7 @@ to all POST requests with a secret token in the HTTP headers. You can make up that ``SECRET_TOKEN`` now. For example, ``superSECRET_token4-POST*requests``. You will put it in the ``vars`` file later. -Every BigchainDB node in a cluster can have a different secret token. +Every BigchainDB node in a BigchainDB network can have a different secret token. To make an HTTP POST request to your BigchainDB node, you must include an HTTP header named ``X-Secret-Access-Token`` and set it equal to your secret token, e.g. @@ -136,7 +136,7 @@ and set it equal to your secret token, e.g. between different Kubernetes clusters, especially if they are running different versions of Kubernetes. We tested this Kubernetes Deployment Template on Azure ACS in February 2018 and at that time ACS was deploying a **Kubernetes 1.7.7** cluster. If you can force your cluster to have that version of Kubernetes, - then you'll increase the likelihood that everything will work in your cluster. + then you'll increase the likelihood that everything will work. 4. Deploy your BigchainDB node inside your new Kubernetes cluster. You will fill up the ``vars`` file, diff --git a/docs/server/source/clusters.md b/docs/server/source/networks.md similarity index 74% rename from docs/server/source/clusters.md rename to docs/server/source/networks.md index 5161f185..22e4f9b0 100644 --- a/docs/server/source/clusters.md +++ b/docs/server/source/networks.md @@ -4,9 +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 ---> -# Clusters +# BigchainDB Networks -A **BigchainDB Cluster** is a set of connected **BigchainDB Nodes**, managed by a **BigchainDB Consortium** (i.e. an organization). Those terms are defined in the [BigchainDB Terminology page](https://docs.bigchaindb.com/en/latest/terminology.html). +A **BigchainDB network** is a set of connected **BigchainDB nodes**, managed by a **BigchainDB consortium** (i.e. an organization). Those terms are defined in the [BigchainDB Terminology page](https://docs.bigchaindb.com/en/latest/terminology.html). ## Consortium Structure & Governance @@ -15,18 +15,18 @@ It must make many decisions, e.g. How will new members be added? Who can read th A governance process is required to make those decisions, and therefore one of the first steps for any new consortium is to specify its governance process (if one doesn't already exist). This documentation doesn't explain how to create a consortium, nor does it outline the possible governance processes. -It's worth noting that the decentralization of a BigchainDB cluster depends, +It's worth noting that the decentralization of a BigchainDB network depends, to some extent, on the decentralization of the associated consortium. See the pages about [decentralization](https://docs.bigchaindb.com/en/latest/decentralized.html) and [node diversity](https://docs.bigchaindb.com/en/latest/diversity.html). -## Cluster DNS Records and SSL Certificates +## DNS Records and SSL Certificates -We now describe how *we* set up the external (public-facing) DNS records for a BigchainDB cluster. Your consortium may opt to do it differently. +We now describe how *we* set up the external (public-facing) DNS records for a BigchainDB network. Your consortium may opt to do it differently. There were several goals: -* Allow external users/clients to connect directly to any BigchainDB node in the cluster (over the internet), if they want. +* Allow external users/clients to connect directly to any BigchainDB node in the network (over the internet), if they want. * Each BigchainDB node operator should get an SSL certificate for their BigchainDB node, so that their BigchainDB node can serve the [BigchainDB HTTP API](http-client-server-api.html) via HTTPS. (The same certificate might also be used to serve the [WebSocket API](websocket-event-stream-api.html).) * There should be no sharing of SSL certificates among BigchainDB node operators. -* Optional: Allow clients to connect to a "random" BigchainDB node in the cluster at one particular domain (or subdomain). +* Optional: Allow clients to connect to a "random" BigchainDB node in the network at one particular domain (or subdomain). ### Node Operator Responsibilities @@ -36,8 +36,8 @@ There were several goals: ### Consortium Responsibilities -Optional: The consortium managing the BigchainDB cluster could register a domain name and set up CNAME records mapping that domain name (or one of its subdomains) to each of the nodes in the cluster. For example, if the consortium registered `bdbcluster.io`, they could set up CNAME records like the following: +Optional: The consortium managing the BigchainDB network could register a domain name and set up CNAME records mapping that domain name (or one of its subdomains) to each of the nodes in the network. For example, if the consortium registered `bdbnetwork.io`, they could set up CNAME records like the following: -* CNAME record mapping `api.bdbcluster.io` to `abc-org73.net` -* CNAME record mapping `api.bdbcluster.io` to `api.dynabob8.io` -* CNAME record mapping `api.bdbcluster.io` to `figmentdb3.ninja` +* CNAME record mapping `api.bdbnetwork.io` to `abc-org73.net` +* CNAME record mapping `api.bdbnetwork.io` to `api.dynabob8.io` +* CNAME record mapping `api.bdbnetwork.io` to `figmentdb3.ninja` diff --git a/docs/server/source/production-nodes/node-assumptions.md b/docs/server/source/production-nodes/node-assumptions.md index b5712cff..962859bd 100644 --- a/docs/server/source/production-nodes/node-assumptions.md +++ b/docs/server/source/production-nodes/node-assumptions.md @@ -8,11 +8,10 @@ Code is Apache-2.0 and docs are CC-BY-4.0 Be sure you know the key BigchainDB terminology: -* [BigchainDB node, BigchainDB cluster and BigchainDB consortium](https://docs.bigchaindb.com/en/latest/terminology.html) +* [BigchainDB node, BigchainDB network and BigchainDB consortium](https://docs.bigchaindb.com/en/latest/terminology.html) * [dev/test node, bare-bones node and production node](../introduction.html) We make some assumptions about production nodes: -1. Production nodes use MongoDB (not RethinkDB, PostgreSQL, Couchbase or whatever). 1. Each production node is set up and managed by an experienced professional system administrator or a team of them. -1. Each production node in a cluster is managed by a different person or team. +1. Each production node in a network is managed by a different person or team. diff --git a/docs/server/source/production-nodes/node-components.md b/docs/server/source/production-nodes/node-components.md index 17d74bce..267c0e88 100644 --- a/docs/server/source/production-nodes/node-components.md +++ b/docs/server/source/production-nodes/node-components.md @@ -17,7 +17,7 @@ It could also include several other components, including: * NGINX or similar, to provide authentication, rate limiting, etc. * An NTP daemon running on all machines running BigchainDB Server or mongod, and possibly other machines -* **Not** MongoDB Automation Agent. It's for automating the deployment of an entire MongoDB cluster, not just one MongoDB node within a cluster. +* Probably _not_ MongoDB Automation Agent. It's for automating the deployment of an entire MongoDB cluster. * MongoDB Monitoring Agent * MongoDB Backup Agent * Log aggregation software From a3dce723bec85fd5c322ef9e0cfd7fdbb5dd487c Mon Sep 17 00:00:00 2001 From: Troy McConaghy Date: Mon, 20 Aug 2018 09:34:55 +0200 Subject: [PATCH 4/5] Problem: Config settings docs missing some configs & inconsistent (#2461) * Problem: Config settings docs missing some configs & inconsistent Solution: Document some undocumented config settings and make the config settings docs page more consistent * Problem: No docs about how config setting env var name determined Solution: Explain and show the relationship between the config-file name and the environment variable name of config settings --- .../source/server-reference/configuration.md | 274 ++++++------------ 1 file changed, 88 insertions(+), 186 deletions(-) diff --git a/docs/server/source/server-reference/configuration.md b/docs/server/source/server-reference/configuration.md index f830992f..9dd892b5 100644 --- a/docs/server/source/server-reference/configuration.md +++ b/docs/server/source/server-reference/configuration.md @@ -6,43 +6,20 @@ Code is Apache-2.0 and docs are CC-BY-4.0 # Configuration Settings -The value of each BigchainDB Server configuration setting is determined according to the following rules: +Every BigchainDB Server configuration setting has two names: a config-file name and an environment variable name. For example, one of the settings has the config-file name `database.host` and the environment variable name `BIGCHAINDB_DATABASE_HOST`. Here are some more examples: + +`database.port` ↔ `BIGCHAINDB_DATABASE_PORT` + +`database.keyfile_passphrase` ↔ `BIGCHAINDB_DATABASE_KEYFILE_PASSPHRASE` + +`server.bind` ↔ `BIGCHAINDB_SERVER_BIND` + +The value of each setting is determined according to the following rules: * If it's set by an environment variable, then use that value * Otherwise, if it's set in a local config file, then use that value * Otherwise, use the default value -For convenience, here's a list of all the relevant environment variables (documented below): - -`BIGCHAINDB_DATABASE_BACKEND`
-`BIGCHAINDB_DATABASE_HOST`
-`BIGCHAINDB_DATABASE_PORT`
-`BIGCHAINDB_DATABASE_NAME`
-`BIGCHAINDB_DATABASE_CONNECTION_TIMEOUT`
-`BIGCHAINDB_DATABASE_MAX_TRIES`
-`BIGCHAINDB_SERVER_BIND`
-`BIGCHAINDB_SERVER_LOGLEVEL`
-`BIGCHAINDB_SERVER_WORKERS`
-`BIGCHAINDB_WSSERVER_SCHEME`
-`BIGCHAINDB_WSSERVER_HOST`
-`BIGCHAINDB_WSSERVER_PORT`
-`BIGCHAINDB_WSSERVER_ADVERTISED_SCHEME`
-`BIGCHAINDB_WSSERVER_ADVERTISED_HOST`
-`BIGCHAINDB_WSSERVER_ADVERTISED_PORT`
-`BIGCHAINDB_CONFIG_PATH`
-`BIGCHAINDB_LOG`
-`BIGCHAINDB_LOG_FILE`
-`BIGCHAINDB_LOG_ERROR_FILE`
-`BIGCHAINDB_LOG_LEVEL_CONSOLE`
-`BIGCHAINDB_LOG_LEVEL_LOGFILE`
-`BIGCHAINDB_LOG_DATEFMT_CONSOLE`
-`BIGCHAINDB_LOG_DATEFMT_LOGFILE`
-`BIGCHAINDB_LOG_FMT_CONSOLE`
-`BIGCHAINDB_LOG_FMT_LOGFILE`
-`BIGCHAINDB_TENDERMINT_HOST`
-`BIGCHAINDB_TENDERMINT_PORT`
- - The local config file is `$HOME/.bigchaindb` by default (a file which might not even exist), but you can tell BigchainDB to use a different file by using the `-c` command-line option, e.g. `bigchaindb -c path/to/config_file.json start` or using the `BIGCHAINDB_CONFIG_PATH` environment variable, e.g. `BIGHAINDB_CONFIG_PATH=.my_bigchaindb_config bigchaindb start`. Note that the `-c` command line option will always take precedence if both the `BIGCHAINDB_CONFIG_PATH` and the `-c` command line option are used. @@ -51,25 +28,30 @@ You can read the current default values in the file [bigchaindb/\_\_init\_\_.py] Running `bigchaindb -y configure localmongodb` will generate a local config file in `$HOME/.bigchaindb` with all the default values. - - ## database.* -The settings with names of the form `database.*` are for the database backend +The settings with names of the form `database.*` are for the backend database (currently only MongoDB). They are: -* `database.backend` is only `localmongodb`, currently. +* `database.backend` can only be `localmongodb`, currently. * `database.host` is the hostname (FQDN) of the backend database. * `database.port` is self-explanatory. * `database.name` is a user-chosen name for the database inside MongoDB, e.g. `bigchain`. -* `database.connection_timeout` is the maximum number of milliseconds that BigchainDB will wait before giving up on one attempt to connect to the database backend. -* `database.max_tries` is the maximum number of times that BigchainDB will try to establish a connection with the database backend. If 0, then it will try forever. +* `database.connection_timeout` is the maximum number of milliseconds that BigchainDB will wait before giving up on one attempt to connect to the backend database. +* `database.max_tries` is the maximum number of times that BigchainDB will try to establish a connection with the backend database. If 0, then it will try forever. +* `database.replicaset` is the name of the MongoDB replica set. The default value is `null` because in BighainDB 2.0+, each BigchainDB node has its own independent MongoDB database and no replica set is necessary. +* `database.login` and `database.password` are the login and password used to authenticate to the backend database, specified in plaintext. +* `database.ssl` determines if BigchainDB connects to MongoDB over TLS/SSL or not. It can be set to `true` or `false`. +* `database.ca_cert`, `database.certfile`, `database.keyfile` and `database.crlfile` are the paths to the CA, signed certificate, private key and certificate revocation list files respectively. +* `database.keyfile_passphrase` is the private key decryption passphrase, specified in plaintext. **Example using environment variables** + ```text export BIGCHAINDB_DATABASE_BACKEND=localmongodb export BIGCHAINDB_DATABASE_HOST=localhost export BIGCHAINDB_DATABASE_PORT=27017 +export BIGCHAINDB_DATABASE_NAME=database8 export BIGCHAINDB_DATABASE_CONNECTION_TIMEOUT=5000 export BIGCHAINDB_DATABASE_MAX_TRIES=3 ``` @@ -77,30 +59,31 @@ export BIGCHAINDB_DATABASE_MAX_TRIES=3 **Default values** If (no environment variables were set and there's no local config file), or you used `bigchaindb -y configure localmongodb` to create a default local config file for a `localmongodb` backend, then the defaults will be: + ```js "database": { "backend": "localmongodb", "host": "localhost", "port": 27017, "name": "bigchain", - "replicaset": null, "connection_timeout": 5000, "max_tries": 3, + "replicaset": null, "login": null, "password": null "ssl": false, "ca_cert": null, - "crlfile": null, "certfile": null, "keyfile": null, + "crlfile": null, "keyfile_passphrase": null, } ``` +## server.* -## server.bind, server.loglevel & server.workers - -These settings are for the [Gunicorn HTTP server](http://gunicorn.org/), which is used to serve the [HTTP client-server API](../http-client-server-api.html). +`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](../http-client-server-api.html). `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). If you want to allow IPv4 connections from anyone, on port 9984, use `0.0.0.0:9984`. In a production setting, we recommend you use Gunicorn behind a reverse proxy server. If Gunicorn and the reverse proxy are running on the same machine, then use `localhost:PORT` where PORT is _not_ 9984 (because the reverse proxy needs to listen on port 9984). Maybe use PORT=9983 in that case because we know 9983 isn't used. If Gunicorn and the reverse proxy are running on different machines, then use `A.B.C.D:9984` where A.B.C.D is the IP address of the reverse proxy. There's [more information about deploying behind a reverse proxy in the Gunicorn documentation](http://docs.gunicorn.org/en/stable/deploy.html). (They call it a proxy.) @@ -111,6 +94,7 @@ for more information. `server.workers` is [the number of worker processes](http://docs.gunicorn.org/en/stable/settings.html#workers) for handling requests. If set to `None`, the value will be (2 × cpu_count + 1). Each worker process has a single thread. The HTTP server will be able to handle `server.workers` requests simultaneously. **Example using environment variables** + ```text export BIGCHAINDB_SERVER_BIND=0.0.0.0:9984 export BIGCHAINDB_SERVER_LOGLEVEL=debug @@ -118,6 +102,7 @@ export BIGCHAINDB_SERVER_WORKERS=5 ``` **Example config file snippet** + ```js "server": { "bind": "0.0.0.0:9984", @@ -127,6 +112,7 @@ export BIGCHAINDB_SERVER_WORKERS=5 ``` **Default values (from a config file)** + ```js "server": { "bind": "localhost:9984", @@ -135,8 +121,10 @@ export BIGCHAINDB_SERVER_WORKERS=5 } ``` +## wsserver.* -## wsserver.scheme, wsserver.host and wsserver.port + +### wsserver.scheme, wsserver.host and wsserver.port These settings are for the [aiohttp server](https://aiohttp.readthedocs.io/en/stable/index.html), @@ -150,6 +138,7 @@ If you want to allow connections from anyone, on port 9985, set `wsserver.host` to 0.0.0.0 and `wsserver.port` to 9985. **Example using environment variables** + ```text export BIGCHAINDB_WSSERVER_SCHEME=ws export BIGCHAINDB_WSSERVER_HOST=0.0.0.0 @@ -157,6 +146,7 @@ export BIGCHAINDB_WSSERVER_PORT=9985 ``` **Example config file snippet** + ```js "wsserver": { "scheme": "wss", @@ -166,6 +156,7 @@ export BIGCHAINDB_WSSERVER_PORT=9985 ``` **Default values (from a config file)** + ```js "wsserver": { "scheme": "ws", @@ -174,7 +165,7 @@ export BIGCHAINDB_WSSERVER_PORT=9985 } ``` -## wsserver.advertised_scheme, wsserver.advertised_host and wsserver.advertised_port +### wsserver.advertised_scheme, wsserver.advertised_host and wsserver.advertised_port These settings are for the advertising the Websocket URL to external clients in the root API endpoint. These configurations might be useful if your deployment @@ -182,6 +173,7 @@ is hosted behind a firewall, NAT, etc. where the exposed public IP or domain is different from where BigchainDB is running. **Example using environment variables** + ```text export BIGCHAINDB_WSSERVER_ADVERTISED_SCHEME=wss export BIGCHAINDB_WSSERVER_ADVERTISED_HOST=mybigchaindb.com @@ -189,6 +181,7 @@ export BIGCHAINDB_WSSERVER_ADVERTISED_PORT=443 ``` **Example config file snippet** + ```js "wsserver": { "advertised_scheme": "wss", @@ -198,6 +191,7 @@ export BIGCHAINDB_WSSERVER_ADVERTISED_PORT=443 ``` **Default values (from a config file)** + ```js "wsserver": { "advertised_scheme": "ws", @@ -206,14 +200,13 @@ export BIGCHAINDB_WSSERVER_ADVERTISED_PORT=443 } ``` -## log +## log.* -The `log` key is expected to point to a mapping (set of key/value pairs) -holding the logging configuration. +The `log.*` settings are to configure logging. -**Example**: +**Example** -``` +```js { "log": { "file": "/var/log/bigchaindb.log", @@ -223,13 +216,14 @@ holding the logging configuration. "datefmt_console": "%Y-%m-%d %H:%M:%S", "datefmt_logfile": "%Y-%m-%d %H:%M:%S", "fmt_console": "%(asctime)s [%(levelname)s] (%(name)s) %(message)s", - "fmt_logfile": "%(asctime)s [%(levelname)s] (%(name)s) %(message)s" + "fmt_logfile": "%(asctime)s [%(levelname)s] (%(name)s) %(message)s", + "granular_levels": {} } ``` -**Defaults to**: +**Default values** -``` +```js { "log": { "file": "~/bigchaindb.log", @@ -239,49 +233,20 @@ holding the logging configuration. "datefmt_console": "%Y-%m-%d %H:%M:%S", "datefmt_logfile": "%Y-%m-%d %H:%M:%S", "fmt_logfile": "[%(asctime)s] [%(levelname)s] (%(name)s) %(message)s (%(processName)-10s - pid: %(process)d)", - "fmt_console": "[%(asctime)s] [%(levelname)s] (%(name)s) %(message)s (%(processName)-10s - pid: %(process)d)" + "fmt_console": "[%(asctime)s] [%(levelname)s] (%(name)s) %(message)s (%(processName)-10s - pid: %(process)d)", + "granular_levels": {} } ``` -The next subsections explain each field of the `log` configuration. +### log.file +The full path to the file where logs should be written. +The user running `bigchaindb` must have write access to the +specified path. -### log.file & log.error_file -The full paths to the files where logs and error logs should be written to. - -**Example**: - -``` -{ - "log": { - "file": "/var/log/bigchaindb/bigchaindb.log" - "error_file": "/var/log/bigchaindb/bigchaindb-errors.log" - } -} -``` - -**Defaults to**: - - * `"~/bigchaindb.log"` - * `"~/bigchaindb-errors.log"` - -Please note that the user running `bigchaindb` must have write access to the -locations. - -#### Log rotation - -Log files have a size limit of 200 MB and will be rotated up to five times. - -For example if we consider the log file setting: - -``` -{ - "log": { - "file": "~/bigchain.log" - } -} -``` - +**Log rotation:** Log files have a size limit of 200 MB +and will be rotated up to five times. +For example, if we `log.file` is set to `"~/bigchain.log"`, then logs would always be written to `bigchain.log`. Each time the file `bigchain.log` reaches 200 MB it would be closed and renamed `bigchain.log.1`. If `bigchain.log.1` and `bigchain.log.2` already exist they @@ -290,141 +255,73 @@ applied up to `bigchain.log.5` after which `bigchain.log.5` would be overwritten by `bigchain.log.4`, thus ending the rotation cycle of whatever logs were in `bigchain.log.5`. +### log.error_file + +Similar to `log.file` (see above), this is the +full path to the file where error logs should be written. ### log.level_console + The log level used to log to the console. Possible allowed values are the ones defined by [Python](https://docs.python.org/3.6/library/logging.html#levels), -but case insensitive for convenience's sake: +but case-insensitive for the sake of convenience: -``` +```text "critical", "error", "warning", "info", "benchmark", "debug", "notset" ``` -**Example**: - -``` -{ - "log": { - "level_console": "info" - } -} -``` - -**Defaults to**: `"info"`. - - ### log.level_logfile + The log level used to log to the log file. Possible allowed values are the ones defined by [Python](https://docs.python.org/3.6/library/logging.html#levels), -but case insensitive for convenience's sake: +but case-insensitive for the sake of convenience: -``` +```text "critical", "error", "warning", "info", "benchmark", "debug", "notset" ``` -**Example**: - -``` -{ - "log": { - "level_file": "info" - } -} -``` - -**Defaults to**: `"info"`. - - ### log.datefmt_console + The format string for the date/time portion of a message, when logged to the console. -**Example**: - -``` -{ - "log": { - "datefmt_console": "%x %X %Z" - } -} -``` - -**Defaults to**: `"%Y-%m-%d %H:%M:%S"`. - For more information on how to construct the format string please consult the -table under Python's documentation of - [`time.strftime(format[, t])`](https://docs.python.org/3.6/library/time.html#time.strftime) +table under [Python's documentation of time.strftime(format[, t])](https://docs.python.org/3.6/library/time.html#time.strftime). ### log.datefmt_logfile + The format string for the date/time portion of a message, when logged to a log file. -**Example**: - -``` -{ - "log": { - "datefmt_logfile": "%c %z" - } -} -``` - -**Defaults to**: `"%Y-%m-%d %H:%M:%S"`. - For more information on how to construct the format string please consult the -table under Python's documentation of - [`time.strftime(format[, t])`](https://docs.python.org/3.6/library/time.html#time.strftime) - +table under [Python's documentation of time.strftime(format[, t])](https://docs.python.org/3.6/library/time.html#time.strftime). ### log.fmt_console + A string used to format the log messages when logged to the console. -**Example**: - -``` -{ - "log": { - "fmt_console": "%(asctime)s [%(levelname)s] %(message)s %(process)d" - } -} -``` - -**Defaults to**: `"[%(asctime)s] [%(levelname)s] (%(name)s) %(message)s (%(processName)-10s - pid: %(process)d)"` - For more information on possible formatting options please consult Python's documentation on -[LogRecord attributes](https://docs.python.org/3.6/library/logging.html#logrecord-attributes) - +[LogRecord attributes](https://docs.python.org/3.6/library/logging.html#logrecord-attributes). ### log.fmt_logfile + A string used to format the log messages when logged to a log file. -**Example**: - -``` -{ - "log": { - "fmt_logfile": "%(asctime)s [%(levelname)s] %(message)s %(process)d" - } -} -``` - -**Defaults to**: `"[%(asctime)s] [%(levelname)s] (%(name)s) %(message)s (%(processName)-10s - pid: %(process)d)"` - For more information on possible formatting options please consult Python's documentation on -[LogRecord attributes](https://docs.python.org/3.6/library/logging.html#logrecord-attributes) - +[LogRecord attributes](https://docs.python.org/3.6/library/logging.html#logrecord-attributes). ### log.granular_levels + Log levels for BigchainDB's modules. This can be useful to control the log level of specific parts of the application. As an example, if you wanted the logging of the `core.py` module to be more verbose, you would set the configuration shown in the example below. -**Example**: +**Example** -``` +```js { "log": { "granular_levels": { @@ -433,17 +330,22 @@ logging of the `core.py` module to be more verbose, you would set the } ``` -**Defaults to**: `{}` +**Default value** -## tendermint.host & tendermint.port +```js +{} +``` -The settings with names of the form `tendermint.*` are for -consensus(Tendermint) backend that we are using: +## tendermint.* -* `tendermint.host` is the hostname (FQDN)/IP address of the tendermint backend. +The settings with names of the form `tendermint.*` tell BigchainDB Server +where it can connect to the node's Tendermint instance. + +* `tendermint.host` is the hostname (FQDN)/IP address of the Tendermint instance. * `tendermint.port` is self-explanatory. **Example using environment variables** + ```text export BIGCHAINDB_TENDERMINT_HOST=tendermint export BIGCHAINDB_TENDERMINT_PORT=26657 @@ -454,6 +356,6 @@ export BIGCHAINDB_TENDERMINT_PORT=26657 ```js "tendermint": { "host": "localhost", - "port": 26657, + "port": 26657 } ``` From 01dba7e883cf1f7d9f187b6d44366bc1847c853a Mon Sep 17 00:00:00 2001 From: Vanshdeep Singh Date: Mon, 20 Aug 2018 16:57:32 +0200 Subject: [PATCH 5/5] Problem: Cannot conclude validator election (#2445) Solution: Gather votes and conclude election when supermajority is achieved --- bigchaindb/backend/localmongodb/query.py | 33 ++- bigchaindb/backend/query.py | 14 + ...nsaction_validator_election_vote_v2.0.yaml | 4 +- bigchaindb/core.py | 36 +-- bigchaindb/lib.py | 9 +- bigchaindb/upsert_validator/__init__.py | 2 +- .../upsert_validator/validator_election.py | 93 +++++- .../validator_election_vote.py | 8 +- .../upsert_validator/validator_utils.py | 37 +++ tests/commands/test_commands.py | 6 +- tests/tendermint/test_core.py | 29 +- tests/tendermint/test_integration.py | 38 --- tests/upsert_validator/conftest.py | 12 +- .../test_validator_election_vote.py | 272 +++++++++++++++++- 14 files changed, 480 insertions(+), 113 deletions(-) create mode 100644 bigchaindb/upsert_validator/validator_utils.py diff --git a/bigchaindb/backend/localmongodb/query.py b/bigchaindb/backend/localmongodb/query.py index 28757337..b4508477 100644 --- a/bigchaindb/backend/localmongodb/query.py +++ b/bigchaindb/backend/localmongodb/query.py @@ -8,7 +8,6 @@ from pymongo import DESCENDING from bigchaindb import backend from bigchaindb.backend.exceptions import DuplicateKeyError -from bigchaindb.common.exceptions import MultipleValidatorOperationError from bigchaindb.backend.utils import module_dispatch_registrar from bigchaindb.backend.localmongodb.connection import LocalMongoDBConnection from bigchaindb.common.transaction import Transaction @@ -115,7 +114,8 @@ def get_spent(conn, transaction_id, output): def get_latest_block(conn): return conn.run( conn.collection('blocks') - .find_one(sort=[('height', DESCENDING)])) + .find_one(projection={'_id': False}, + sort=[('height', DESCENDING)])) @register_query(LocalMongoDBConnection) @@ -282,13 +282,15 @@ def get_pre_commit_state(conn, commit_id): @register_query(LocalMongoDBConnection) -def store_validator_set(conn, validator_update): - try: - return conn.run( - conn.collection('validators') - .insert_one(validator_update)) - except DuplicateKeyError: - raise MultipleValidatorOperationError('Validator update already exists') +def store_validator_set(conn, validators_update): + height = validators_update['height'] + return conn.run( + conn.collection('validators').replace_one( + {'height': height}, + validators_update, + upsert=True + ) + ) @register_query(LocalMongoDBConnection) @@ -305,3 +307,16 @@ def get_validator_set(conn, height=None): ) return list(cursor)[0] + + +@register_query(LocalMongoDBConnection) +def get_asset_tokens_for_public_key(conn, asset_id, public_key): + query = {'outputs.public_keys': [public_key], + 'asset.id': asset_id} + + cursor = conn.run( + conn.collection('transactions').aggregate([ + {'$match': query}, + {'$project': {'_id': False}} + ])) + return cursor diff --git a/bigchaindb/backend/query.py b/bigchaindb/backend/query.py index 278b33da..7527c863 100644 --- a/bigchaindb/backend/query.py +++ b/bigchaindb/backend/query.py @@ -372,3 +372,17 @@ def get_validator_set(conn, height): """ raise NotImplementedError + + +@singledispatch +def get_asset_tokens_for_public_key(connection, asset_id, + public_key, operation): + """Retrieve a list of tokens of type `asset_id` that are owned by the `public_key`. + Args: + asset_id (str): Id of the token. + public_key (str): base58 encoded public key + operation: filter transaction based on `operation` + Returns: + Iterator of transaction that list given owner in conditions. + """ + raise NotImplementedError diff --git a/bigchaindb/common/schema/transaction_validator_election_vote_v2.0.yaml b/bigchaindb/common/schema/transaction_validator_election_vote_v2.0.yaml index db3cba9e..c17fb229 100644 --- a/bigchaindb/common/schema/transaction_validator_election_vote_v2.0.yaml +++ b/bigchaindb/common/schema/transaction_validator_election_vote_v2.0.yaml @@ -10,7 +10,9 @@ required: - operation - outputs properties: - operation: "VALIDATOR_ELECTION_VOTE" + operation: + type: string + value: "VALIDATOR_ELECTION_VOTE" outputs: type: array items: diff --git a/bigchaindb/core.py b/bigchaindb/core.py index 90be42b5..67b48df5 100644 --- a/bigchaindb/core.py +++ b/bigchaindb/core.py @@ -6,7 +6,6 @@ with Tendermint. """ import logging -import codecs from abci.application import BaseApplication from abci.types_pb2 import ( @@ -17,8 +16,6 @@ from abci.types_pb2 import ( ResponseDeliverTx, ResponseEndBlock, ResponseCommit, - Validator, - PubKey ) from bigchaindb import BigchainDB @@ -26,6 +23,8 @@ from bigchaindb.tendermint_utils import (decode_transaction, calculate_hash) from bigchaindb.lib import Block, PreCommitState from bigchaindb.backend.query import PRE_COMMIT_ID +from bigchaindb.upsert_validator import ValidatorElection +import bigchaindb.upsert_validator.validator_utils as vutils CodeTypeOk = 0 @@ -52,7 +51,7 @@ class App(BaseApplication): def init_chain(self, genesis): """Initialize chain with block of height 0""" - validator_set = [decode_validator(v) for v in genesis.validators] + validator_set = [vutils.decode_validator(v) for v in genesis.validators] block = Block(app_hash='', height=0, transactions=[]) self.bigchaindb.store_block(block._asdict()) self.bigchaindb.store_validator_set(1, validator_set) @@ -141,11 +140,11 @@ class App(BaseApplication): else: self.block_txn_hash = block['app_hash'] - # TODO: calculate if an election has concluded - # NOTE: ensure the local validator set is updated - # validator_updates = self.bigchaindb.get_validator_update() - # validator_updates = [encode_validator(v) for v in validator_updates] - validator_updates = [] + # Check if the current block concluded any validator elections and + # update the locally tracked validator set + validator_updates = ValidatorElection.get_validator_update(self.bigchaindb, + self.new_height, + self.block_transactions) # Store pre-commit state to recover in case there is a crash # during `commit` @@ -176,22 +175,3 @@ class App(BaseApplication): self.block_txn_ids) logger.benchmark('COMMIT_BLOCK, height:%s', self.new_height) return ResponseCommit(data=data) - - -def encode_validator(v): - ed25519_public_key = v['pub_key']['data'] - # NOTE: tendermint expects public to be encoded in go-amino format - - pub_key = PubKey(type='ed25519', - data=bytes.fromhex(ed25519_public_key)) - - return Validator(pub_key=pub_key, - address=b'', - power=v['power']) - - -def decode_validator(v): - return {'address': codecs.encode(v.address, 'hex').decode().upper().rstrip('\n'), - 'pub_key': {'type': v.pub_key.type, - 'data': codecs.encode(v.pub_key.data, 'base64').decode().rstrip('\n')}, - 'voting_power': v.power} diff --git a/bigchaindb/lib.py b/bigchaindb/lib.py index c9761cb1..a8cc14fa 100644 --- a/bigchaindb/lib.py +++ b/bigchaindb/lib.py @@ -29,6 +29,7 @@ from bigchaindb.tendermint_utils import encode_transaction, merkleroot from bigchaindb import exceptions as core_exceptions from bigchaindb.consensus import BaseConsensusRules + logger = logging.getLogger(__name__) @@ -442,16 +443,8 @@ class BigchainDB(object): def get_validators(self, height=None): result = backend.query.get_validator_set(self.connection, height) validators = result['validators'] - for v in validators: - v.pop('address') - v['voting_power'] = int(v['voting_power']) - return validators - def get_validator_update(self): - update = backend.query.get_validator_update(self.connection) - return [update['validator']] if update else [] - def delete_validator_update(self): return backend.query.delete_validator_update(self.connection) diff --git a/bigchaindb/upsert_validator/__init__.py b/bigchaindb/upsert_validator/__init__.py index 3af785a7..90a02a0b 100644 --- a/bigchaindb/upsert_validator/__init__.py +++ b/bigchaindb/upsert_validator/__init__.py @@ -3,5 +3,5 @@ # Code is Apache-2.0 and docs are CC-BY-4.0 -from bigchaindb.upsert_validator.validator_election import ValidatorElection # noqa from bigchaindb.upsert_validator.validator_election_vote import ValidatorElectionVote # noqa +from bigchaindb.upsert_validator.validator_election import ValidatorElection # noqa diff --git a/bigchaindb/upsert_validator/validator_election.py b/bigchaindb/upsert_validator/validator_election.py index ea21a32c..a2c68412 100644 --- a/bigchaindb/upsert_validator/validator_election.py +++ b/bigchaindb/upsert_validator/validator_election.py @@ -2,6 +2,9 @@ # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) # Code is Apache-2.0 and docs are CC-BY-4.0 +import base58 + +from bigchaindb import backend from bigchaindb.common.exceptions import (InvalidSignature, MultipleInputsError, InvalidProposer, @@ -15,6 +18,8 @@ from bigchaindb.common.schema import (_validate_schema, TX_SCHEMA_VALIDATOR_ELECTION, TX_SCHEMA_COMMON, TX_SCHEMA_CREATE) +from . import ValidatorElectionVote +from .validator_utils import (new_validator_set, encode_validator) class ValidatorElection(Transaction): @@ -32,15 +37,15 @@ class ValidatorElection(Transaction): super().__init__(operation, asset, inputs, outputs, metadata, version, hash_id) @classmethod - def current_validators(cls, bigchain): + def get_validators(cls, bigchain, height=None): """Return a dictionary of validators with key as `public_key` and value as the `voting_power` """ validators = {} - for validator in bigchain.get_validators(): + for validator in bigchain.get_validators(height): # NOTE: we assume that Tendermint encodes public key in base64 - public_key = public_key_from_ed25519_key(key_from_base64(validator['pub_key']['value'])) + public_key = public_key_from_ed25519_key(key_from_base64(validator['pub_key']['data'])) validators[public_key] = validator['voting_power'] return validators @@ -50,7 +55,7 @@ class ValidatorElection(Transaction): """Convert validator dictionary to a recipient list for `Transaction`""" recipients = [] - for public_key, voting_power in cls.current_validators(bigchain).items(): + for public_key, voting_power in cls.get_validators(bigchain).items(): recipients.append(([public_key], voting_power)) return recipients @@ -84,7 +89,7 @@ class ValidatorElection(Transaction): bigchain (BigchainDB): an instantiated bigchaindb.lib.BigchainDB object. Returns: - `True` if the election is valid + ValidatorElection object Raises: ValidationError: If the election is invalid @@ -99,7 +104,7 @@ class ValidatorElection(Transaction): if not self.inputs_valid(input_conditions): raise InvalidSignature('Transaction signature is invalid.') - current_validators = self.current_validators(bigchain) + current_validators = self.get_validators(bigchain) # NOTE: Proposer should be a single node if len(self.inputs) != 1 or len(self.inputs[0].owners_before) != 1: @@ -118,7 +123,7 @@ class ValidatorElection(Transaction): if not self.is_same_topology(current_validators, self.outputs): raise UnequalValidatorSet('Validator set much be exactly same to the outputs of election') - return True + return self @classmethod def generate(cls, initiator, voters, election_data, metadata=None): @@ -145,3 +150,77 @@ class ValidatorElection(Transaction): @classmethod def transfer(cls, tx_signers, recipients, metadata=None, asset=None): raise NotImplementedError + + @classmethod + def to_public_key(cls, election_id): + return base58.b58encode(bytes.fromhex(election_id)) + + @classmethod + def count_votes(cls, election_pk, transactions, getter=getattr): + votes = 0 + for txn in transactions: + if getter(txn, 'operation') == 'VALIDATOR_ELECTION_VOTE': + for output in getter(txn, 'outputs'): + # NOTE: We enforce that a valid vote to election id will have only + # election_pk in the output public keys, including any other public key + # along with election_pk will lead to vote being not considered valid. + if len(getter(output, 'public_keys')) == 1 and [election_pk] == getter(output, 'public_keys'): + votes = votes + int(getter(output, 'amount')) + return votes + + def get_commited_votes(self, bigchain, election_pk=None): + if election_pk is None: + election_pk = self.to_public_key(self.id) + txns = list(backend.query.get_asset_tokens_for_public_key(bigchain.connection, + self.id, + election_pk)) + return self.count_votes(election_pk, txns, dict.get) + + @classmethod + def has_concluded(cls, bigchain, election_id, current_votes=[], height=None): + """Check if the given `election_id` can be concluded or not + NOTE: + * Election is concluded iff the current validator set is exactly equal + to the validator set encoded in election outputs + * Election can concluded only if the current votes achieves a supermajority + """ + election = bigchain.get_transaction(election_id) + + if election: + election_pk = election.to_public_key(election.id) + votes_commited = election.get_commited_votes(bigchain, election_pk) + votes_current = election.count_votes(election_pk, current_votes) + current_validators = election.get_validators(bigchain, height) + + if election.is_same_topology(current_validators, election.outputs): + total_votes = sum(current_validators.values()) + if (votes_commited < (2/3)*total_votes) and \ + (votes_commited + votes_current >= (2/3)*total_votes): + return election + return False + + @classmethod + def get_validator_update(cls, bigchain, new_height, txns): + votes = {} + for txn in txns: + if not isinstance(txn, ValidatorElectionVote): + continue + + election_id = txn.asset['id'] + election_votes = votes.get(election_id, []) + election_votes.append(txn) + votes[election_id] = election_votes + + election = cls.has_concluded(bigchain, election_id, election_votes, new_height) + # Once an election concludes any other conclusion for the same + # or any other election is invalidated + if election: + # The new validator set comes into effect from height = new_height+1 + validator_updates = [election.asset['data']] + curr_validator_set = bigchain.get_validators(new_height) + updated_validator_set = new_validator_set(curr_validator_set, + new_height, validator_updates) + + bigchain.store_validator_set(new_height+1, updated_validator_set) + return [encode_validator(election.asset['data'])] + return [] diff --git a/bigchaindb/upsert_validator/validator_election_vote.py b/bigchaindb/upsert_validator/validator_election_vote.py index bec373ae..7620b289 100644 --- a/bigchaindb/upsert_validator/validator_election_vote.py +++ b/bigchaindb/upsert_validator/validator_election_vote.py @@ -2,8 +2,6 @@ # SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0) # Code is Apache-2.0 and docs are CC-BY-4.0 -import base58 - from bigchaindb.common.transaction import Transaction from bigchaindb.common.schema import (_validate_schema, TX_SCHEMA_COMMON, @@ -30,7 +28,7 @@ class ValidatorElectionVote(Transaction): bigchain (BigchainDB): an instantiated bigchaindb.lib.BigchainDB object. Returns: - `True` if the election vote is valid + ValidatorElectionVote object Raises: ValidationError: If the election vote is invalid @@ -38,10 +36,6 @@ class ValidatorElectionVote(Transaction): self.validate_transfer_inputs(bigchain, current_transactions) return self - @classmethod - def to_public_key(cls, election_id): - return base58.b58encode(bytes.fromhex(election_id)) - @classmethod def generate(cls, inputs, recipients, election_id, metadata=None): (inputs, outputs) = cls.validate_transfer(inputs, recipients, election_id, metadata) diff --git a/bigchaindb/upsert_validator/validator_utils.py b/bigchaindb/upsert_validator/validator_utils.py new file mode 100644 index 00000000..7cb924d8 --- /dev/null +++ b/bigchaindb/upsert_validator/validator_utils.py @@ -0,0 +1,37 @@ +import codecs + +from abci.types_pb2 import (Validator, + PubKey) +from bigchaindb.tendermint_utils import public_key_to_base64 + + +def encode_validator(v): + ed25519_public_key = v['public_key'] + # NOTE: tendermint expects public to be encoded in go-amino format + pub_key = PubKey(type='ed25519', + data=bytes.fromhex(ed25519_public_key)) + return Validator(pub_key=pub_key, + address=b'', + power=v['power']) + + +def decode_validator(v): + return {'pub_key': {'type': v.pub_key.type, + 'data': codecs.encode(v.pub_key.data, 'base64').decode().rstrip('\n')}, + 'voting_power': v.power} + + +def new_validator_set(validators, height, updates): + validators_dict = {} + for v in validators: + validators_dict[v['pub_key']['data']] = v + + updates_dict = {} + for u in updates: + public_key64 = public_key_to_base64(u['public_key']) + updates_dict[public_key64] = {'pub_key': {'type': 'ed25519', + 'data': public_key64}, + 'voting_power': u['power']} + + new_validators_dict = {**validators_dict, **updates_dict} + return list(new_validators_dict.values()) diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index 5bca4423..c82a5e29 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -388,9 +388,9 @@ def test_upsert_validator_new_with_tendermint(b, priv_validator_path, user_sk, m def test_upsert_validator_new_without_tendermint(b, priv_validator_path, user_sk, monkeypatch): from bigchaindb.commands.bigchaindb import run_upsert_validator_new - def mock_get(): + def mock_get(height): return [ - {'pub_key': {'value': 'zL/DasvKulXZzhSNFwx4cLRXKkSM9GPK7Y0nZ4FEylM=', + {'pub_key': {'data': 'zL/DasvKulXZzhSNFwx4cLRXKkSM9GPK7Y0nZ4FEylM=', 'type': 'tendermint/PubKeyEd25519'}, 'voting_power': 10} ] @@ -402,8 +402,6 @@ def test_upsert_validator_new_without_tendermint(b, priv_validator_path, user_sk b.get_validators = mock_get b.write_transaction = mock_write - monkeypatch.setattr('requests.get', mock_get) - args = Namespace(action='new', public_key='CJxdItf4lz2PwEf4SmYNAu/c/VpmX39JEgC5YpH7fxg=', power=1, diff --git a/tests/tendermint/test_core.py b/tests/tendermint/test_core.py index 4814a488..e698d45a 100644 --- a/tests/tendermint/test_core.py +++ b/tests/tendermint/test_core.py @@ -10,7 +10,11 @@ from abci.types_pb2 import ( RequestEndBlock ) -from bigchaindb.core import CodeTypeOk, CodeTypeError +from bigchaindb.core import (CodeTypeOk, + CodeTypeError, + ) +from bigchaindb.upsert_validator.validator_utils import new_validator_set +from bigchaindb.tendermint_utils import public_key_to_base64 pytestmark = [pytest.mark.tendermint, pytest.mark.bdb] @@ -220,3 +224,26 @@ def test_store_pre_commit_state_in_end_block(b, alice, init_chain_request): assert resp['commit_id'] == PRE_COMMIT_ID assert resp['height'] == 100 assert resp['transactions'] == [tx.id] + + +def test_new_validator_set(b): + node1 = {'pub_key': {'type': 'ed25519', + 'data': 'FxjS2/8AFYoIUqF6AcePTc87qOT7e4WGgH+sGCpTUDQ='}, + 'voting_power': 10} + node1_new_power = {'public_key': '1718D2DBFF00158A0852A17A01C78F4DCF3BA8E4FB7B8586807FAC182A535034', + 'power': 20} + node2 = {'public_key': '1888A353B181715CA2554701D06C1665BC42C5D936C55EA9C5DBCBDB8B3F02A3', + 'power': 10} + + validators = [node1] + updates = [node1_new_power, node2] + b.store_validator_set(1, validators) + updated_validator_set = new_validator_set(b.get_validators(1), 1, updates) + + updated_validators = [] + for u in updates: + updated_validators.append({'pub_key': {'type': 'ed25519', + 'data': public_key_to_base64(u['public_key'])}, + 'voting_power': u['power']}) + + assert updated_validator_set == updated_validators diff --git a/tests/tendermint/test_integration.py b/tests/tendermint/test_integration.py index 80c714e3..1a8250bd 100644 --- a/tests/tendermint/test_integration.py +++ b/tests/tendermint/test_integration.py @@ -11,7 +11,6 @@ import pytest from abci.server import ProtocolHandler from abci.encoding import read_messages -from copy import deepcopy from io import BytesIO @@ -109,43 +108,6 @@ def test_app(tb, init_chain_request): assert block0['app_hash'] == new_block_hash -@pytest.mark.skip -@pytest.mark.abci -def test_upsert_validator(b, alice): - from bigchaindb.backend.query import VALIDATOR_UPDATE_ID - from bigchaindb.backend import query, connect - from bigchaindb.models import Transaction - from bigchaindb.tendermint_utils import public_key_to_base64 - import time - - conn = connect() - power = 1 - public_key = '9B3119650DF82B9A5D8A12E38953EA47475C09F0C48A4E6A0ECE182944B24403' - - validator = {'pub_key': {'type': 'AC26791624DE60', - 'data': public_key}, - 'power': power} - validator_update = {'validator': validator, - 'update_id': VALIDATOR_UPDATE_ID} - - query.store_validator_update(conn, deepcopy(validator_update)) - - tx = Transaction.create([alice.public_key], - [([alice.public_key], 1)], - asset=None)\ - .sign([alice.private_key]) - - code, message = b.write_transaction(tx, 'broadcast_tx_commit') - assert code == 202 - time.sleep(5) - - validators = b.get_validators() - validators = [(v['pub_key']['value'], v['voting_power']) for v in validators] - - public_key64 = public_key_to_base64(public_key) - assert ((public_key64, str(power)) in validators) - - @pytest.mark.abci def test_post_transaction_responses(tendermint_ws_url, b): from bigchaindb.common.crypto import generate_key_pair diff --git a/tests/upsert_validator/conftest.py b/tests/upsert_validator/conftest.py index b2745493..64bf2279 100644 --- a/tests/upsert_validator/conftest.py +++ b/tests/upsert_validator/conftest.py @@ -26,11 +26,11 @@ def new_validator(): def mock_get_validators(network_validators): - def validator_set(): + def validator_set(height): validators = [] for public_key, power in network_validators.items(): validators.append({ - 'pub_key': {'type': 'AC26791624DE60', 'value': public_key}, + 'pub_key': {'type': 'AC26791624DE60', 'data': public_key}, 'voting_power': power }) return validators @@ -44,3 +44,11 @@ def valid_election(b_mock, node_key, new_validator): return ValidatorElection.generate([node_key.public_key], voters, new_validator, None).sign([node_key.private_key]) + + +@pytest.fixture +def valid_election_b(b, node_key, new_validator): + voters = ValidatorElection.recipients(b) + return ValidatorElection.generate([node_key.public_key], + voters, + new_validator, None).sign([node_key.private_key]) diff --git a/tests/upsert_validator/test_validator_election_vote.py b/tests/upsert_validator/test_validator_election_vote.py index eceecbce..c0798224 100644 --- a/tests/upsert_validator/test_validator_election_vote.py +++ b/tests/upsert_validator/test_validator_election_vote.py @@ -3,14 +3,20 @@ # Code is Apache-2.0 and docs are CC-BY-4.0 import pytest +import codecs -from bigchaindb.upsert_validator import ValidatorElectionVote +from bigchaindb.tendermint_utils import public_key_to_base64 +from bigchaindb.upsert_validator import ValidatorElection, ValidatorElectionVote from bigchaindb.common.exceptions import AmountError +from bigchaindb.common.crypto import generate_key_pair +from bigchaindb.common.exceptions import ValidationError -pytestmark = [pytest.mark.tendermint, pytest.mark.bdb] +pytestmark = [pytest.mark.execute] +@pytest.mark.tendermint +@pytest.mark.bdb def test_upsert_validator_valid_election_vote(b_mock, valid_election, ed25519_node_keys): b_mock.store_bulk_transactions([valid_election]) @@ -19,7 +25,7 @@ def test_upsert_validator_valid_election_vote(b_mock, valid_election, ed25519_no public_key0 = input0.owners_before[0] key0 = ed25519_node_keys[public_key0] - election_pub_key = ValidatorElectionVote.to_public_key(valid_election.id) + election_pub_key = ValidatorElection.to_public_key(valid_election.id) vote = ValidatorElectionVote.generate([input0], [([election_pub_key], votes)], @@ -28,9 +34,29 @@ def test_upsert_validator_valid_election_vote(b_mock, valid_election, ed25519_no assert vote.validate(b_mock) -def test_upsert_validator_delegate_election_vote(b_mock, valid_election, ed25519_node_keys): - from bigchaindb.common.crypto import generate_key_pair +@pytest.mark.tendermint +@pytest.mark.bdb +def test_upsert_validator_valid_non_election_vote(b_mock, valid_election, ed25519_node_keys): + b_mock.store_bulk_transactions([valid_election]) + input0 = valid_election.to_inputs()[0] + votes = valid_election.outputs[0].amount + public_key0 = input0.owners_before[0] + key0 = ed25519_node_keys[public_key0] + + election_pub_key = ValidatorElection.to_public_key(valid_election.id) + + # Ensure that threshold conditions are now allowed + with pytest.raises(ValidationError): + ValidatorElectionVote.generate([input0], + [([election_pub_key, key0.public_key], votes)], + election_id=valid_election.id)\ + .sign([key0.private_key]) + + +@pytest.mark.tendermint +@pytest.mark.bdb +def test_upsert_validator_delegate_election_vote(b_mock, valid_election, ed25519_node_keys): alice = generate_key_pair() b_mock.store_bulk_transactions([valid_election]) @@ -48,7 +74,7 @@ def test_upsert_validator_delegate_election_vote(b_mock, valid_election, ed25519 assert delegate_vote.validate(b_mock) b_mock.store_bulk_transactions([delegate_vote]) - election_pub_key = ValidatorElectionVote.to_public_key(valid_election.id) + election_pub_key = ValidatorElection.to_public_key(valid_election.id) alice_votes = delegate_vote.to_inputs()[0] alice_casted_vote = ValidatorElectionVote.generate([alice_votes], @@ -65,6 +91,8 @@ def test_upsert_validator_delegate_election_vote(b_mock, valid_election, ed25519 assert key0_casted_vote.validate(b_mock) +@pytest.mark.tendermint +@pytest.mark.bdb def test_upsert_validator_invalid_election_vote(b_mock, valid_election, ed25519_node_keys): b_mock.store_bulk_transactions([valid_election]) @@ -73,7 +101,7 @@ def test_upsert_validator_invalid_election_vote(b_mock, valid_election, ed25519_ public_key0 = input0.owners_before[0] key0 = ed25519_node_keys[public_key0] - election_pub_key = ValidatorElectionVote.to_public_key(valid_election.id) + election_pub_key = ValidatorElection.to_public_key(valid_election.id) vote = ValidatorElectionVote.generate([input0], [([election_pub_key], votes+1)], @@ -82,3 +110,233 @@ def test_upsert_validator_invalid_election_vote(b_mock, valid_election, ed25519_ with pytest.raises(AmountError): assert vote.validate(b_mock) + + +@pytest.mark.tendermint +@pytest.mark.bdb +def test_valid_election_votes_received(b_mock, valid_election, ed25519_node_keys): + alice = generate_key_pair() + b_mock.store_bulk_transactions([valid_election]) + assert valid_election.get_commited_votes(b_mock) == 0 + + input0 = valid_election.to_inputs()[0] + votes = valid_election.outputs[0].amount + public_key0 = input0.owners_before[0] + key0 = ed25519_node_keys[public_key0] + + # delegate some votes to alice + delegate_vote = ValidatorElectionVote.generate([input0], + [([alice.public_key], 4), ([key0.public_key], votes-4)], + election_id=valid_election.id)\ + .sign([key0.private_key]) + b_mock.store_bulk_transactions([delegate_vote]) + assert valid_election.get_commited_votes(b_mock) == 0 + + election_public_key = ValidatorElection.to_public_key(valid_election.id) + alice_votes = delegate_vote.to_inputs()[0] + key0_votes = delegate_vote.to_inputs()[1] + + alice_casted_vote = ValidatorElectionVote.generate([alice_votes], + [([election_public_key], 2), ([alice.public_key], 2)], + election_id=valid_election.id)\ + .sign([alice.private_key]) + + assert alice_casted_vote.validate(b_mock) + b_mock.store_bulk_transactions([alice_casted_vote]) + + # Check if the delegated vote is count as valid vote + assert valid_election.get_commited_votes(b_mock) == 2 + + key0_casted_vote = ValidatorElectionVote.generate([key0_votes], + [([election_public_key], votes-4)], + election_id=valid_election.id)\ + .sign([key0.private_key]) + + assert key0_casted_vote.validate(b_mock) + b_mock.store_bulk_transactions([key0_casted_vote]) + + assert valid_election.get_commited_votes(b_mock) == votes-2 + + +@pytest.mark.tendermint +@pytest.mark.bdb +def test_valid_election_conclude(b_mock, valid_election, ed25519_node_keys): + + # Node 0: cast vote + tx_vote0 = gen_vote(valid_election, 0, ed25519_node_keys) + + # check if the vote is valid even before the election doesn't exist + with pytest.raises(ValidationError): + assert tx_vote0.validate(b_mock) + + # store election + b_mock.store_bulk_transactions([valid_election]) + # cannot conclude election as not votes exist + assert not ValidatorElection.has_concluded(b_mock, valid_election.id) + + # validate vote + assert tx_vote0.validate(b_mock) + assert not ValidatorElection.has_concluded(b_mock, valid_election.id, [tx_vote0]) + + b_mock.store_bulk_transactions([tx_vote0]) + assert not ValidatorElection.has_concluded(b_mock, valid_election.id) + + # Node 1: cast vote + tx_vote1 = gen_vote(valid_election, 1, ed25519_node_keys) + + # Node 2: cast vote + tx_vote2 = gen_vote(valid_election, 2, ed25519_node_keys) + + # Node 3: cast vote + tx_vote3 = gen_vote(valid_election, 3, ed25519_node_keys) + + assert tx_vote1.validate(b_mock) + assert not ValidatorElection.has_concluded(b_mock, valid_election.id, [tx_vote1]) + + # 2/3 is achieved in the same block so the election can be.has_concludedd + assert ValidatorElection.has_concluded(b_mock, valid_election.id, [tx_vote1, tx_vote2]) + + b_mock.store_bulk_transactions([tx_vote1]) + assert not ValidatorElection.has_concluded(b_mock, valid_election.id) + + assert tx_vote2.validate(b_mock) + assert tx_vote3.validate(b_mock) + + # conclusion can be triggered my different votes in the same block + assert ValidatorElection.has_concluded(b_mock, valid_election.id, [tx_vote2]) + assert ValidatorElection.has_concluded(b_mock, valid_election.id, [tx_vote2, tx_vote3]) + + b_mock.store_bulk_transactions([tx_vote2]) + + # Once the blockchain records >2/3 of the votes the election is assumed to be.has_concludedd + # so any invocation of `.has_concluded` for that election should return False + assert not ValidatorElection.has_concluded(b_mock, valid_election.id) + + # Vote is still valid but the election cannot be.has_concludedd as it it assmed that it has + # been.has_concludedd before + assert tx_vote3.validate(b_mock) + assert not ValidatorElection.has_concluded(b_mock, valid_election.id, [tx_vote3]) + + +@pytest.mark.abci +def test_upsert_validator(b, node_key, node_keys, new_validator, ed25519_node_keys): + import time + import requests + + (node_pub, _) = list(node_keys.items())[0] + + validators = [{'pub_key': {'type': 'ed25519', + 'data': node_pub}, + 'voting_power': 10}] + + latest_block = b.get_latest_block() + # reset the validator set + b.store_validator_set(latest_block['height'], validators) + + power = 1 + public_key = '9B3119650DF82B9A5D8A12E38953EA47475C09F0C48A4E6A0ECE182944B24403' + public_key64 = public_key_to_base64(public_key) + new_validator = {'public_key': public_key, + 'node_id': 'some_node_id', + 'power': power} + + voters = ValidatorElection.recipients(b) + election = ValidatorElection.generate([node_key.public_key], + voters, + new_validator, None).sign([node_key.private_key]) + code, message = b.write_transaction(election, 'broadcast_tx_commit') + assert code == 202 + time.sleep(3) + + assert b.get_transaction(election.id) + + tx_vote = gen_vote(election, 0, ed25519_node_keys) + assert tx_vote.validate(b) + code, message = b.write_transaction(tx_vote, 'broadcast_tx_commit') + assert code == 202 + time.sleep(3) + + resp = requests.get(b.endpoint + 'validators') + validator_pub_keys = [] + for v in resp.json()['result']['validators']: + validator_pub_keys.append(v['pub_key']['value']) + + assert (public_key64 in validator_pub_keys) + new_validator_set = b.get_validators() + validator_pub_keys = [] + for v in new_validator_set: + validator_pub_keys.append(v['pub_key']['data']) + + assert (public_key64 in validator_pub_keys) + + +@pytest.mark.tendermint +@pytest.mark.bdb +def test_get_validator_update(b, node_keys, node_key, ed25519_node_keys): + reset_validator_set(b, node_keys, 1) + + power = 1 + public_key = '9B3119650DF82B9A5D8A12E38953EA47475C09F0C48A4E6A0ECE182944B24403' + public_key64 = public_key_to_base64(public_key) + new_validator = {'public_key': public_key, + 'node_id': 'some_node_id', + 'power': power} + voters = ValidatorElection.recipients(b) + election = ValidatorElection.generate([node_key.public_key], + voters, + new_validator).sign([node_key.private_key]) + # store election + b.store_bulk_transactions([election]) + + tx_vote0 = gen_vote(election, 0, ed25519_node_keys) + tx_vote1 = gen_vote(election, 1, ed25519_node_keys) + tx_vote2 = gen_vote(election, 2, ed25519_node_keys) + + assert not ValidatorElection.has_concluded(b, election.id, [tx_vote0]) + assert not ValidatorElection.has_concluded(b, election.id, [tx_vote0, tx_vote1]) + assert ValidatorElection.has_concluded(b, election.id, [tx_vote0, tx_vote1, tx_vote2]) + + assert ValidatorElection.get_validator_update(b, 4, [tx_vote0]) == [] + assert ValidatorElection.get_validator_update(b, 4, [tx_vote0, tx_vote1]) == [] + + update = ValidatorElection.get_validator_update(b, 4, [tx_vote0, tx_vote1, tx_vote2]) + update_public_key = codecs.encode(update[0].pub_key.data, 'base64').decode().rstrip('\n') + assert len(update) == 1 + assert update_public_key == public_key64 + + b.store_bulk_transactions([tx_vote0, tx_vote1]) + + update = ValidatorElection.get_validator_update(b, 4, [tx_vote2]) + print('update', update) + update_public_key = codecs.encode(update[0].pub_key.data, 'base64').decode().rstrip('\n') + assert len(update) == 1 + assert update_public_key == public_key64 + + +# ============================================================================ +# Helper functions +# ============================================================================ +def to_inputs(election, i, ed25519_node_keys): + input0 = election.to_inputs()[i] + votes = election.outputs[i].amount + public_key0 = input0.owners_before[0] + key0 = ed25519_node_keys[public_key0] + return (input0, votes, key0) + + +def gen_vote(election, i, ed25519_node_keys): + (input_i, votes_i, key_i) = to_inputs(election, i, ed25519_node_keys) + election_pub_key = ValidatorElection.to_public_key(election.id) + return ValidatorElectionVote.generate([input_i], + [([election_pub_key], votes_i)], + election_id=election.id)\ + .sign([key_i.private_key]) + + +def reset_validator_set(b, node_keys, height): + validators = [] + for (node_pub, _) in node_keys.items(): + validators.append({'pub_key': {'type': 'ed25519', + 'data': node_pub}, + 'voting_power': 10}) + b.store_validator_set(height, validators)