From 14ffe739a5a3b212b6b59a49003432a9e41196c2 Mon Sep 17 00:00:00 2001 From: ryan Date: Tue, 16 Feb 2016 10:04:33 +0100 Subject: [PATCH 01/44] initial commit --- bigchaindb/core.py | 7 +++++++ bigchaindb/monitor.py | 11 +++++++++++ docker-compose.yml | 26 ++++++++++++++++++++++++++ 3 files changed, 44 insertions(+) create mode 100644 bigchaindb/monitor.py diff --git a/bigchaindb/core.py b/bigchaindb/core.py index 286336a0..e1382390 100644 --- a/bigchaindb/core.py +++ b/bigchaindb/core.py @@ -10,7 +10,9 @@ import bigchaindb import bigchaindb.config_utils from bigchaindb import exceptions from bigchaindb.crypto import hash_data, PublicKey, PrivateKey, generate_key_pair +from bigchaindb.monitor import Monitor +c = Monitor() class GenesisBlockAlreadyExistsError(Exception): pass @@ -67,6 +69,7 @@ class Bigchain(object): def reconnect(self): return r.connect(host=self.host, port=self.port, db=self.dbname) + @c.timer('create_transaction', rate=0.01) def create_transaction(self, current_owner, new_owner, tx_input, operation, payload=None): """Create a new transaction @@ -176,6 +179,7 @@ class Bigchain(object): public_key = PublicKey(public_key_base58) return public_key.verify(self.serialize(data), signature) + @c.timer('write_transaction', rate=0.01) def write_transaction(self, signed_transaction): """Write the transaction to bigchain. @@ -312,6 +316,7 @@ class Bigchain(object): return owned + @c.timer('validate_transaction', rate=0.01) def validate_transaction(self, transaction): """Validate a transaction. @@ -389,6 +394,7 @@ class Bigchain(object): exceptions.InvalidHash, exceptions.InvalidSignature): return False + @c.timer('create_block') def create_block(self, validated_transactions): """Creates a block given a list of `validated_transactions`. @@ -424,6 +430,7 @@ class Bigchain(object): return block + @c.timer('validate_block') # TODO: check that the votings structure is correctly constructed def validate_block(self, block): """Validate a block. diff --git a/bigchaindb/monitor.py b/bigchaindb/monitor.py new file mode 100644 index 00000000..5e88cffa --- /dev/null +++ b/bigchaindb/monitor.py @@ -0,0 +1,11 @@ +import statsd +from os import getppid +from platform import node + +class Monitor(statsd.StatsClient): + def __init__(self, *args, **kwargs): + if not kwargs: + kwargs = {} + if not kwargs.get('prefix'): + kwargs['prefix'] = '{hostname}_{pid}.'.format(hostname=node(), pid=getppid()) + super().__init__(*args, **kwargs) diff --git a/docker-compose.yml b/docker-compose.yml index fb9727a4..73f48f95 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -24,7 +24,33 @@ bigchaindb: - ~/.bigchaindb_docker:/root/.bigchaindb_docker links: - rethinkdb + - statsd:localhost environment: BIGCHAIN_DATABASE_HOST: rethinkdb BIGCHAINDB_CONFIG_PATH: /root/.bigchaindb_docker/config command: bigchaindb start + +influxdb: + image: tutum/influxdb + ports: + - "8083:8083" + - "8086:8086" + expose: + - "8090" + - "8099" + environment: + PRE_CREATE_DB: "telegraf" + +grafana: + build: ~/grafana-bigchaindb-docker + ports: + - "3000:3000" + links: + - influxdb:localhost + +statsd: + image: rhsimplex/docker-telegraf-statsd + expose: + - "8125/udp" + links: + - influxdb:localhost \ No newline at end of file From fc045d1174e4a52472d7c397598f5629b43983d4 Mon Sep 17 00:00:00 2001 From: ryan Date: Wed, 17 Feb 2016 14:55:55 +0100 Subject: [PATCH 02/44] timing adjustments --- bigchaindb/core.py | 4 ++-- docker-compose.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bigchaindb/core.py b/bigchaindb/core.py index 1c876a4c..fda96240 100644 --- a/bigchaindb/core.py +++ b/bigchaindb/core.py @@ -12,7 +12,7 @@ from bigchaindb import exceptions from bigchaindb.crypto import hash_data, PublicKey, PrivateKey, generate_key_pair from bigchaindb.monitor import Monitor -c = Monitor() +c = Monitor(host='statsd_1') class GenesisBlockAlreadyExistsError(Exception): pass @@ -317,7 +317,7 @@ class Bigchain(object): return owned - @c.timer('validate_transaction', rate=0.01) + @c.timer('validate_transaction')#, rate=0.01) def validate_transaction(self, transaction): """Validate a transaction. diff --git a/docker-compose.yml b/docker-compose.yml index 73f48f95..a529028c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -50,7 +50,7 @@ grafana: statsd: image: rhsimplex/docker-telegraf-statsd - expose: - - "8125/udp" + ports: + - "8125:8125/udp" links: - influxdb:localhost \ No newline at end of file From 2350ec156402ad3c479c73d47c05877045bae65d Mon Sep 17 00:00:00 2001 From: ryan Date: Wed, 17 Feb 2016 16:09:08 +0100 Subject: [PATCH 03/44] more measurements --- bigchaindb/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigchaindb/core.py b/bigchaindb/core.py index fda96240..9981eaf2 100644 --- a/bigchaindb/core.py +++ b/bigchaindb/core.py @@ -395,7 +395,6 @@ class Bigchain(object): exceptions.InvalidHash, exceptions.InvalidSignature): return False - @c.timer('create_block') def create_block(self, validated_transactions): """Creates a block given a list of `validated_transactions`. @@ -476,6 +475,7 @@ class Bigchain(object): except Exception: return False + @c.timer('write_block') def write_block(self, block, durability='soft'): """Write a block to bigchain. From f32425d6ff6d73437be2007439304b4bceaef50c Mon Sep 17 00:00:00 2001 From: ryan Date: Thu, 18 Feb 2016 09:39:20 +0100 Subject: [PATCH 04/44] reorganize yml --- docker-compose-monitor.yml | 92 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 docker-compose-monitor.yml diff --git a/docker-compose-monitor.yml b/docker-compose-monitor.yml new file mode 100644 index 00000000..34dc11f6 --- /dev/null +++ b/docker-compose-monitor.yml @@ -0,0 +1,92 @@ +rethinkdb-1: + image: rethinkdb + ports: + - "9999:8080" + - "28015:28015" + - "29015:29015" + volumes_from: + - rethinkdb-data-1 + +rethinkdb-data-1: + image: rethinkdb + volumes: + - /data + command: "true" + +bigchaindb-1: + build: . + volumes: + - ./bigchaindb:/usr/src/app/bigchaindb + - ./tests:/usr/src/app/tests + - ./docs:/usr/src/app/docs + - ./setup.py:/usr/src/app/setup.py + - ./setup.cfg:/usr/src/app/setup.cfg + - ./pytest.ini:/usr/src/app/pytest.ini + - ~/.bigchaindb_docker:/root/.bigchaindb_docker + links: + - rethinkdb-1:rethinkdb + - statsd:localhost + environment: + BIGCHAIN_DATABASE_HOST: rethinkdb + BIGCHAINDB_CONFIG_PATH: /root/.bigchaindb_docker/config + command: bigchaindb start + +rethinkdb-2: + image: rethinkdb + ports: + - "9998:8080" + - "28015:28015" + - "29015:29015" + links: + - rethinkdb-1:localhost + volumes_from: + - rethinkdb-data-2 + +rethinkdb-data-2: + image: rethinkdb + volumes: + - /data + command: "true" + +bigchaindb-2: + build: . + volumes: + - ./bigchaindb:/usr/src/app/bigchaindb + - ./tests:/usr/src/app/tests + - ./docs:/usr/src/app/docs + - ./setup.py:/usr/src/app/setup.py + - ./setup.cfg:/usr/src/app/setup.cfg + - ./pytest.ini:/usr/src/app/pytest.ini + - ~/.bigchaindb_docker:/root/.bigchaindb_docker + links: + - rethinkdb-2:rethinkdb + - statsd:localhost + environment: + BIGCHAIN_DATABASE_HOST: rethinkdb + BIGCHAINDB_CONFIG_PATH: /root/.bigchaindb_docker/config + command: bigchaindb start + +influxdb: + image: tutum/influxdb + ports: + - "8083:8083" + - "8086:8086" + expose: + - "8090" + - "8099" + environment: + PRE_CREATE_DB: "telegraf" + +grafana: + build: ~/grafana-bigchaindb-docker + ports: + - "3000:3000" + links: + - influxdb:localhost + +statsd: + image: rhsimplex/docker-telegraf-statsd + ports: + - "8125:8125/udp" + links: + - influxdb:localhost \ No newline at end of file From a037e7f0423b26783bd7bd176195cdbdcad52e6a Mon Sep 17 00:00:00 2001 From: ryan Date: Thu, 18 Feb 2016 09:39:42 +0100 Subject: [PATCH 05/44] reorganize yml --- docker-compose.yml | 28 +--------------------------- 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index a529028c..646fc946 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -24,33 +24,7 @@ bigchaindb: - ~/.bigchaindb_docker:/root/.bigchaindb_docker links: - rethinkdb - - statsd:localhost environment: BIGCHAIN_DATABASE_HOST: rethinkdb BIGCHAINDB_CONFIG_PATH: /root/.bigchaindb_docker/config - command: bigchaindb start - -influxdb: - image: tutum/influxdb - ports: - - "8083:8083" - - "8086:8086" - expose: - - "8090" - - "8099" - environment: - PRE_CREATE_DB: "telegraf" - -grafana: - build: ~/grafana-bigchaindb-docker - ports: - - "3000:3000" - links: - - influxdb:localhost - -statsd: - image: rhsimplex/docker-telegraf-statsd - ports: - - "8125:8125/udp" - links: - - influxdb:localhost \ No newline at end of file + command: bigchaindb start \ No newline at end of file From b9279431cd10666e944bc165d4530aac2741429f Mon Sep 17 00:00:00 2001 From: ryan Date: Thu, 18 Feb 2016 11:39:10 +0100 Subject: [PATCH 06/44] small changes --- bigchaindb/monitor.py | 3 +-- docker-compose-monitor.yml | 8 ++++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/bigchaindb/monitor.py b/bigchaindb/monitor.py index 5e88cffa..d3a33472 100644 --- a/bigchaindb/monitor.py +++ b/bigchaindb/monitor.py @@ -1,5 +1,4 @@ import statsd -from os import getppid from platform import node class Monitor(statsd.StatsClient): @@ -7,5 +6,5 @@ class Monitor(statsd.StatsClient): if not kwargs: kwargs = {} if not kwargs.get('prefix'): - kwargs['prefix'] = '{hostname}_{pid}.'.format(hostname=node(), pid=getppid()) + kwargs['prefix'] = '{hostname}.'.format(hostname=node()) super().__init__(*args, **kwargs) diff --git a/docker-compose-monitor.yml b/docker-compose-monitor.yml index 34dc11f6..92dfeb19 100644 --- a/docker-compose-monitor.yml +++ b/docker-compose-monitor.yml @@ -2,8 +2,8 @@ rethinkdb-1: image: rethinkdb ports: - "9999:8080" - - "28015:28015" - - "29015:29015" + - "28015" + - "29015" volumes_from: - rethinkdb-data-1 @@ -35,8 +35,8 @@ rethinkdb-2: image: rethinkdb ports: - "9998:8080" - - "28015:28015" - - "29015:29015" + - "28015" + - "29015" links: - rethinkdb-1:localhost volumes_from: From cd9048d861e4625d2be4cf11e541b8d73ae7614d Mon Sep 17 00:00:00 2001 From: ryan Date: Thu, 18 Feb 2016 16:18:45 +0100 Subject: [PATCH 07/44] validation tx queue, comments --- bigchaindb/block.py | 7 +++++++ bigchaindb/core.py | 2 ++ 2 files changed, 9 insertions(+) diff --git a/bigchaindb/block.py b/bigchaindb/block.py index ed1257e9..144ebef8 100644 --- a/bigchaindb/block.py +++ b/bigchaindb/block.py @@ -5,10 +5,16 @@ import queue import rethinkdb as r from bigchaindb import Bigchain +from bigchaindb.monitor import Monitor + logger = logging.getLogger(__name__) +# obviously the hostname should come from an environment variable or setting +# http://i.imgur.com/qciaOed.jpg +c = Monitor(host='statsd_1') + class Block(object): @@ -32,6 +38,7 @@ class Block(object): b = Bigchain() while True: + c.gauge('tx_queue_gauge', self.q_tx_to_validate.qsize(), rate=0.01) tx = self.q_new_transaction.get() # poison pill diff --git a/bigchaindb/core.py b/bigchaindb/core.py index 9981eaf2..c5221c26 100644 --- a/bigchaindb/core.py +++ b/bigchaindb/core.py @@ -12,6 +12,8 @@ from bigchaindb import exceptions from bigchaindb.crypto import hash_data, PublicKey, PrivateKey, generate_key_pair from bigchaindb.monitor import Monitor +# obviously the hostname should come from an environment variable or setting + c = Monitor(host='statsd_1') class GenesisBlockAlreadyExistsError(Exception): From 131d7fad14575a6123f6ecca45e47d65f7370d8b Mon Sep 17 00:00:00 2001 From: ryan Date: Mon, 22 Feb 2016 10:17:16 +0100 Subject: [PATCH 08/44] setup changes --- bigchaindb/__init__.py | 10 ++++++++-- bigchaindb/block.py | 5 +++-- bigchaindb/commands/bigchain.py | 4 ++++ bigchaindb/core.py | 9 ++++----- bigchaindb/monitor.py | 20 ++++++++++++++++++++ 5 files changed, 39 insertions(+), 9 deletions(-) diff --git a/bigchaindb/__init__.py b/bigchaindb/__init__.py index aec03314..4bb76c44 100644 --- a/bigchaindb/__init__.py +++ b/bigchaindb/__init__.py @@ -1,7 +1,6 @@ import os import copy -from bigchaindb.core import Bigchain # noqa def e(key, default=None, conv=None): @@ -35,10 +34,17 @@ config = { 'private': e('BIGCHAIN_KEYPAIR_PRIVATE') }, 'keyring': [ - ] + ], + 'statsd': { + 'host': e('BIGCHAIN_STATSD_HOST', default='localhost'), + 'port': e('BIGCHAIN_STATSD_PORT', default=8125), + 'rate': e('BIGCHAIN_STATSD_SAMPLERATE', default=0.01) + } } # We need to maintain a backup copy of the original config dict in case # the user wants to reconfigure the node. Check ``bigchaindb.config_utils`` # for more info. _config = copy.deepcopy(config) +from bigchaindb.core import Bigchain # noqa + diff --git a/bigchaindb/block.py b/bigchaindb/block.py index 144ebef8..c4e7e17a 100644 --- a/bigchaindb/block.py +++ b/bigchaindb/block.py @@ -4,6 +4,7 @@ import queue import rethinkdb as r +import bigchaindb from bigchaindb import Bigchain from bigchaindb.monitor import Monitor @@ -13,7 +14,7 @@ logger = logging.getLogger(__name__) # obviously the hostname should come from an environment variable or setting # http://i.imgur.com/qciaOed.jpg -c = Monitor(host='statsd_1') +c = Monitor() class Block(object): @@ -38,7 +39,7 @@ class Block(object): b = Bigchain() while True: - c.gauge('tx_queue_gauge', self.q_tx_to_validate.qsize(), rate=0.01) + c.gauge('tx_queue_gauge', self.q_tx_to_validate.qsize(), rate=bigchaindb.config['statsd']['rate']) tx = self.q_new_transaction.get() # poison pill diff --git a/bigchaindb/commands/bigchain.py b/bigchaindb/commands/bigchain.py index 7ea95851..21d157e3 100644 --- a/bigchaindb/commands/bigchain.py +++ b/bigchaindb/commands/bigchain.py @@ -56,6 +56,10 @@ def run_configure(args, skip_if_exists=False): val = conf['database'][key] conf['database'][key] = input('Database {}? (default `{}`): '.format(key, val)) or val + for key in ('host', 'port', 'rate'): + val = conf['statsd'][key] + conf['statsd'][key] = input('Statsd {}? (default `{}`): '.format(key, val)) or val + bigchaindb.config_utils.write_config(conf, config_path) print('Ready to go!') diff --git a/bigchaindb/core.py b/bigchaindb/core.py index c5221c26..8a962a1b 100644 --- a/bigchaindb/core.py +++ b/bigchaindb/core.py @@ -12,9 +12,8 @@ from bigchaindb import exceptions from bigchaindb.crypto import hash_data, PublicKey, PrivateKey, generate_key_pair from bigchaindb.monitor import Monitor -# obviously the hostname should come from an environment variable or setting +c = Monitor() -c = Monitor(host='statsd_1') class GenesisBlockAlreadyExistsError(Exception): pass @@ -72,7 +71,7 @@ class Bigchain(object): def reconnect(self): return r.connect(host=self.host, port=self.port, db=self.dbname) - @c.timer('create_transaction', rate=0.01) + @c.timer('create_transaction', rate=bigchaindb.config['statsd']['rate']) def create_transaction(self, current_owner, new_owner, tx_input, operation, payload=None): """Create a new transaction @@ -182,7 +181,7 @@ class Bigchain(object): public_key = PublicKey(public_key_base58) return public_key.verify(self.serialize(data), signature) - @c.timer('write_transaction', rate=0.01) + @c.timer('write_transaction', rate=bigchaindb.config['statsd']['rate']) def write_transaction(self, signed_transaction): """Write the transaction to bigchain. @@ -319,7 +318,7 @@ class Bigchain(object): return owned - @c.timer('validate_transaction')#, rate=0.01) + @c.timer('validate_transaction', rate=bigchaindb.config['statsd']['rate']) def validate_transaction(self, transaction): """Validate a transaction. diff --git a/bigchaindb/monitor.py b/bigchaindb/monitor.py index d3a33472..be5c27cd 100644 --- a/bigchaindb/monitor.py +++ b/bigchaindb/monitor.py @@ -1,10 +1,30 @@ import statsd from platform import node +import bigchaindb +from bigchaindb import config_utils + class Monitor(statsd.StatsClient): + """Set up statsd monitoring + + """ def __init__(self, *args, **kwargs): + """Overrides statsd client, fixing prefix to messages and loading configuration + + Args: + *args: arguments (identical to Statsclient) + **kwargs: keyword arguments (identical to Statsclient) + """ + config_utils.autoconfigure() + if not kwargs: kwargs = {} + + # set prefix, parameters from configuration file if not kwargs.get('prefix'): kwargs['prefix'] = '{hostname}.'.format(hostname=node()) + if not kwargs.get('host'): + kwargs['host'] = bigchaindb.config['statsd']['host'] + if not kwargs.get('port'): + kwargs['port'] = bigchaindb.config['statsd']['port'] super().__init__(*args, **kwargs) From 5445a043b8cb82b96da5266d20c307805c06a4fc Mon Sep 17 00:00:00 2001 From: ryan Date: Mon, 22 Feb 2016 10:59:26 +0100 Subject: [PATCH 09/44] docker-compose-monitor.yml --- docker-compose-monitor.yml | 68 -------------------------------------- 1 file changed, 68 deletions(-) diff --git a/docker-compose-monitor.yml b/docker-compose-monitor.yml index 92dfeb19..7c48bd0e 100644 --- a/docker-compose-monitor.yml +++ b/docker-compose-monitor.yml @@ -1,71 +1,3 @@ -rethinkdb-1: - image: rethinkdb - ports: - - "9999:8080" - - "28015" - - "29015" - volumes_from: - - rethinkdb-data-1 - -rethinkdb-data-1: - image: rethinkdb - volumes: - - /data - command: "true" - -bigchaindb-1: - build: . - volumes: - - ./bigchaindb:/usr/src/app/bigchaindb - - ./tests:/usr/src/app/tests - - ./docs:/usr/src/app/docs - - ./setup.py:/usr/src/app/setup.py - - ./setup.cfg:/usr/src/app/setup.cfg - - ./pytest.ini:/usr/src/app/pytest.ini - - ~/.bigchaindb_docker:/root/.bigchaindb_docker - links: - - rethinkdb-1:rethinkdb - - statsd:localhost - environment: - BIGCHAIN_DATABASE_HOST: rethinkdb - BIGCHAINDB_CONFIG_PATH: /root/.bigchaindb_docker/config - command: bigchaindb start - -rethinkdb-2: - image: rethinkdb - ports: - - "9998:8080" - - "28015" - - "29015" - links: - - rethinkdb-1:localhost - volumes_from: - - rethinkdb-data-2 - -rethinkdb-data-2: - image: rethinkdb - volumes: - - /data - command: "true" - -bigchaindb-2: - build: . - volumes: - - ./bigchaindb:/usr/src/app/bigchaindb - - ./tests:/usr/src/app/tests - - ./docs:/usr/src/app/docs - - ./setup.py:/usr/src/app/setup.py - - ./setup.cfg:/usr/src/app/setup.cfg - - ./pytest.ini:/usr/src/app/pytest.ini - - ~/.bigchaindb_docker:/root/.bigchaindb_docker - links: - - rethinkdb-2:rethinkdb - - statsd:localhost - environment: - BIGCHAIN_DATABASE_HOST: rethinkdb - BIGCHAINDB_CONFIG_PATH: /root/.bigchaindb_docker/config - command: bigchaindb start - influxdb: image: tutum/influxdb ports: From 82e18826953ed8eb0465b65357791532eb372db1 Mon Sep 17 00:00:00 2001 From: ryan Date: Mon, 22 Feb 2016 14:15:13 +0100 Subject: [PATCH 10/44] trim docker compose --- docker-compose-monitor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose-monitor.yml b/docker-compose-monitor.yml index 7c48bd0e..ebede989 100644 --- a/docker-compose-monitor.yml +++ b/docker-compose-monitor.yml @@ -10,7 +10,7 @@ influxdb: PRE_CREATE_DB: "telegraf" grafana: - build: ~/grafana-bigchaindb-docker + build: rhsimplex/grafana-bigchaindb-docker ports: - "3000:3000" links: From 09f244e325bd0fee9b008d2c63c9f065ea4accfa Mon Sep 17 00:00:00 2001 From: ryan Date: Mon, 22 Feb 2016 14:15:44 +0100 Subject: [PATCH 11/44] read inputs in correct place --- bigchaindb/commands/bigchain.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bigchaindb/commands/bigchain.py b/bigchaindb/commands/bigchain.py index a27a012c..33d37822 100644 --- a/bigchaindb/commands/bigchain.py +++ b/bigchaindb/commands/bigchain.py @@ -59,9 +59,9 @@ def run_configure(args, skip_if_exists=False): val = conf['database'][key] conf['database'][key] = input('Database {}? (default `{}`): '.format(key, val)) or val - for key in ('host', 'port', 'rate'): - val = conf['statsd'][key] - conf['statsd'][key] = input('Statsd {}? (default `{}`): '.format(key, val)) or val + for key in ('host', 'port', 'rate'): + val = conf['statsd'][key] + conf['statsd'][key] = input('Statsd {}? (default `{}`): '.format(key, val)) or val bigchaindb.config_utils.write_config(conf, config_path) print('Ready to go!') From 63fb14dcc5ee5b897b511de2dfeee140f6bb984c Mon Sep 17 00:00:00 2001 From: ryan Date: Mon, 22 Feb 2016 14:16:02 +0100 Subject: [PATCH 12/44] adjust tests accordingly --- tests/test_commands.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_commands.py b/tests/test_commands.py index b3c1ae58..faa455d0 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -60,6 +60,7 @@ def mock_bigchaindb_backup_config(monkeypatch): config = { 'keypair': {}, 'database': {'host': 'host', 'port': 12345, 'name': 'adbname'}, + 'statsd': {'host': 'host', 'port': 12345, 'rate': 0.1}, } monkeypatch.setattr('bigchaindb._config', config) From f71a166e796c47ae18837ab4ea62722f2c825c03 Mon Sep 17 00:00:00 2001 From: ryan Date: Mon, 22 Feb 2016 14:17:06 +0100 Subject: [PATCH 13/44] trim docker-compose --- docker-compose-monitor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose-monitor.yml b/docker-compose-monitor.yml index ebede989..ea4faf54 100644 --- a/docker-compose-monitor.yml +++ b/docker-compose-monitor.yml @@ -10,7 +10,7 @@ influxdb: PRE_CREATE_DB: "telegraf" grafana: - build: rhsimplex/grafana-bigchaindb-docker + image: rhsimplex/grafana-bigchaindb-docker ports: - "3000:3000" links: From b4a023a9d28d67692e86ce216be19e37cea544eb Mon Sep 17 00:00:00 2001 From: ryan Date: Mon, 22 Feb 2016 14:59:19 +0100 Subject: [PATCH 14/44] update README.md --- README.md | 17 +++++++++++++++++ bigchaindb/block.py | 2 -- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 948b879c..bd1a06e6 100644 --- a/README.md +++ b/README.md @@ -79,3 +79,20 @@ $ bigchaindb show-config ``` $ py.test -v ``` + +#### Monitoring + +BigchainDB uses [statsd](https://github.com/etsy/statsd) for monitoring. To fully take advantage of this functionality requires some additional infrastructure: an agent to listen for metrics (e.g. [telegraf](https://github.com/influxdata/telegraf)), a time-series database (e.g. [influxdb](https://influxdata.com/time-series-platform/influxdb/), and a frontend to display analytics (e.g. [Grafana](http://grafana.org/)). + +For ease of use, we've provided a docker compose file that sets up all these services for testing. Simply run in the BigchainDB directory: + +```sh +$ docker-compose -f docker-compose-monitor.yml build +$ docker-compose -f docker-compose-monitor.yml up +``` + +and point a browser tab to `http://localhost:3000/dashboard/script/bigchaindb_dashboard.js`. Login and password are `admin` by default. If BigchainDB is running and processing transactions, you should see analytics—if not, start BigchainDB as above, and refresh the page after a few seconds. + +If you're not interested in monitoring, don't worry: BigchainDB will function just fine without any monitoring setup. + +Feel free to modify the [custom Grafana dashboard](https://github.com/rhsimplex/grafana-bigchaindb-docker/blob/master/bigchaindb_dashboard.js) to your liking! \ No newline at end of file diff --git a/bigchaindb/block.py b/bigchaindb/block.py index c4e7e17a..11b6b7ed 100644 --- a/bigchaindb/block.py +++ b/bigchaindb/block.py @@ -12,8 +12,6 @@ from bigchaindb.monitor import Monitor logger = logging.getLogger(__name__) -# obviously the hostname should come from an environment variable or setting -# http://i.imgur.com/qciaOed.jpg c = Monitor() From 024817a4ac4ddb66166951f912fad4e8a490885f Mon Sep 17 00:00:00 2001 From: Rodolphe Marques Date: Mon, 22 Feb 2016 19:45:55 +0100 Subject: [PATCH 15/44] Initial implementation of the fix --- bigchaindb/core.py | 20 ++++--- tests/db/test_voter.py | 116 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+), 7 deletions(-) diff --git a/bigchaindb/core.py b/bigchaindb/core.py index 76d90a58..1f0eccea 100644 --- a/bigchaindb/core.py +++ b/bigchaindb/core.py @@ -272,20 +272,25 @@ class Bigchain(object): The transaction that used the `txid` as an input if it exists else it returns `None` """ - # checks if an input was already spent # checks if the bigchain has any transaction with input `transaction_id` - response = r.table('bigchain').concat_map(lambda doc: doc['block']['transactions'])\ + response = r.table('bigchain').group('block_number')\ + .concat_map(lambda doc: doc['block']['transactions'])\ .filter(lambda transaction: transaction['transaction']['input'] == txid).run(self.conn) + # the query returns a dictionary in which keys are block numbers and values are list of transactions + # with that using that input inside the block. For it to be correct: + # - There should be at most one block with transactions using that input + # - There should be at most one transaction with that input + # a transaction_id should have been spent at most one time - transactions = list(response) - if transactions: - if len(transactions) != 1: + if response: + if len(response) > 1 or len(*response.values()) > 1: raise Exception('`{}` was spent more then once. There is a problem with the chain'.format( txid)) else: - return transactions[0] + response = list(response.items()) + return (response[0][0], response[0][1][0]) else: return None @@ -466,7 +471,8 @@ class Bigchain(object): try: self.validate_block(block) return True - except Exception: + except Exception as e: + print(e) return False def write_block(self, block, durability='soft'): diff --git a/tests/db/test_voter.py b/tests/db/test_voter.py index 550c5da9..30ae8091 100644 --- a/tests/db/test_voter.py +++ b/tests/db/test_voter.py @@ -47,6 +47,122 @@ class TestBigchainVoter(object): assert vote['node_pubkey'] == b.me assert PublicKey(b.me).verify(b.serialize(vote['vote']), vote['signature']) is True + def test_valid_block_voting_with_create_transaction(self, b): + q_new_block = mp.Queue() + + genesis = b.create_genesis_block() + + # create a `CREATE` transaction + test_user_priv, test_user_pub = b.generate_keys() + tx = b.create_transaction(b.me, test_user_pub, None, 'CREATE') + tx_signed = b.sign_transaction(tx, b.me_private) + assert b.is_valid_transaction(tx_signed) + + # create valid block + block = b.create_block([tx_signed]) + # assert block is valid + assert b.is_valid_block(block) + b.write_block(block, durability='hard') + + # create queue and voter + voter = Voter(q_new_block) + + # vote + voter.start() + # wait for vote to be written + time.sleep(1) + voter.kill() + + # retrive block from bigchain + blocks = list(r.table('bigchain') + .order_by(r.asc((r.row['block']['timestamp']))) + .run(b.conn)) + + + # validate vote + assert len(blocks[1]['votes']) == 1 + vote = blocks[1]['votes'][0] + + assert vote['vote']['voting_for_block'] == block['id'] + assert vote['vote']['previous_block'] == genesis['id'] + assert vote['vote']['is_block_valid'] is True + assert vote['vote']['invalid_reason'] is None + assert vote['node_pubkey'] == b.me + assert PublicKey(b.me).verify(b.serialize(vote['vote']), vote['signature']) is True + + def test_valid_block_voting_with_transfer_transactions(self, b): + q_new_block = mp.Queue() + + genesis = b.create_genesis_block() + + # create a `CREATE` transaction + test_user_priv, test_user_pub = b.generate_keys() + tx = b.create_transaction(b.me, test_user_pub, None, 'CREATE') + tx_signed = b.sign_transaction(tx, b.me_private) + assert b.is_valid_transaction(tx_signed) + + # create valid block + block = b.create_block([tx_signed]) + # assert block is valid + assert b.is_valid_block(block) + b.write_block(block, durability='hard') + + # create queue and voter + voter = Voter(q_new_block) + + # vote + voter.start() + # wait for vote to be written + time.sleep(1) + voter.kill() + + # retrive block from bigchain + blocks = list(r.table('bigchain') + .order_by(r.asc((r.row['block']['timestamp']))) + .run(b.conn)) + + + # validate vote + assert len(blocks[1]['votes']) == 1 + + + # create a `TRANSFER` transaction + test_user2_priv, test_user2_pub = b.generate_keys() + tx2 = b.create_transaction(test_user_pub, test_user2_pub, tx['id'], 'TRANSFER') + tx2_signed = b.sign_transaction(tx2, test_user_priv) + assert b.is_valid_transaction(tx2_signed) + + # create valid block + block = b.create_block([tx2_signed]) + # assert block is valid + assert b.is_valid_block(block) + b.write_block(block, durability='hard') + + # create queue and voter + voter = Voter(q_new_block) + + # vote + voter.start() + # wait for vote to be written + time.sleep(1) + voter.kill() + + # retrive block from bigchain + blocks = list(r.table('bigchain') + .order_by(r.asc((r.row['block']['timestamp']))) + .run(b.conn)) + + + # validate vote + assert len(blocks[2]['votes']) == 1 + + vote = blocks[2]['votes'][0] + + assert vote['vote']['voting_for_block'] == block['id'] + assert vote['vote']['is_block_valid'] is True + assert vote['vote']['invalid_reason'] is None + assert vote['node_pubkey'] == b.me + assert PublicKey(b.me).verify(b.serialize(vote['vote']), vote['signature']) is True def test_invalid_block_voting(self, b, user_public_key): # create queue and voter From 9b662ff1991cb5125fa199f7af6ce0259db18ca2 Mon Sep 17 00:00:00 2001 From: Sylvain Bellemare Date: Mon, 22 Feb 2016 16:56:36 +0100 Subject: [PATCH 16/44] Add test for monitor --- tests/test_monitor.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 tests/test_monitor.py diff --git a/tests/test_monitor.py b/tests/test_monitor.py new file mode 100644 index 00000000..a138b9b1 --- /dev/null +++ b/tests/test_monitor.py @@ -0,0 +1,14 @@ +from platform import node + + +def test_monitor_class_init_defaults(): + import bigchaindb + from bigchaindb.monitor import Monitor + monitor = Monitor() + assert monitor + assert len(monitor._addr) == 2 + # TODO get value from config + # assert monitor._addr[0] == bigchaindb.config['statsd']['host'] + assert monitor._addr[0] == '127.0.0.1' + assert monitor._addr[1] == bigchaindb.config['statsd']['port'] + assert monitor._prefix == node() + '.' From 0d9de5497635f1d7888b684a9244e56de8c3f555 Mon Sep 17 00:00:00 2001 From: Rodolphe Marques Date: Tue, 23 Feb 2016 10:42:42 +0100 Subject: [PATCH 17/44] get_spent also returns the block id containing the the transaction that used txid as an input --- bigchaindb/core.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/bigchaindb/core.py b/bigchaindb/core.py index 1f0eccea..97cbbed5 100644 --- a/bigchaindb/core.py +++ b/bigchaindb/core.py @@ -269,12 +269,13 @@ class Bigchain(object): txid (str): transaction id. Returns: - The transaction that used the `txid` as an input if it exists else it returns `None` + A tuple with the block id and the transactions that used the `txid` as an input if + it exists else it returns `None` """ # checks if an input was already spent # checks if the bigchain has any transaction with input `transaction_id` - response = r.table('bigchain').group('block_number')\ + response = r.table('bigchain').group('id')\ .concat_map(lambda doc: doc['block']['transactions'])\ .filter(lambda transaction: transaction['transaction']['input'] == txid).run(self.conn) @@ -283,14 +284,19 @@ class Bigchain(object): # - There should be at most one block with transactions using that input # - There should be at most one transaction with that input + # flatten to dictionary into a list of [(block['id'], tx), ...] + transactions = [] + for k, v in response.items(): + for tx in v: + transactions.append((k, tx)) + # a transaction_id should have been spent at most one time - if response: - if len(response) > 1 or len(*response.values()) > 1: + if transactions: + if len(transactions) > 1: raise Exception('`{}` was spent more then once. There is a problem with the chain'.format( txid)) else: - response = list(response.items()) - return (response[0][0], response[0][1][0]) + return transactions[0] else: return None From 4326c863ac2efc74c79d703953ac5ed0c1f781cd Mon Sep 17 00:00:00 2001 From: Rodolphe Marques Date: Tue, 23 Feb 2016 13:48:31 +0100 Subject: [PATCH 18/44] Fixed how validate_transaction handles double spends. Create tests. Fixed some flake8 warnings --- bigchaindb/core.py | 29 ++++++++--------------------- tests/db/test_bigchain_api.py | 29 ++++++++++++++++++++++------- tests/db/test_voter.py | 12 +----------- 3 files changed, 31 insertions(+), 39 deletions(-) diff --git a/bigchaindb/core.py b/bigchaindb/core.py index 97cbbed5..ce3c5bb7 100644 --- a/bigchaindb/core.py +++ b/bigchaindb/core.py @@ -269,30 +269,18 @@ class Bigchain(object): txid (str): transaction id. Returns: - A tuple with the block id and the transactions that used the `txid` as an input if - it exists else it returns `None` + The transaction that used the `txid` as an input if it exists else it returns `None` """ # checks if an input was already spent # checks if the bigchain has any transaction with input `transaction_id` - response = r.table('bigchain').group('id')\ - .concat_map(lambda doc: doc['block']['transactions'])\ + response = r.table('bigchain').concat_map(lambda doc: doc['block']['transactions'])\ .filter(lambda transaction: transaction['transaction']['input'] == txid).run(self.conn) - # the query returns a dictionary in which keys are block numbers and values are list of transactions - # with that using that input inside the block. For it to be correct: - # - There should be at most one block with transactions using that input - # - There should be at most one transaction with that input - - # flatten to dictionary into a list of [(block['id'], tx), ...] - transactions = [] - for k, v in response.items(): - for tx in v: - transactions.append((k, tx)) - # a transaction_id should have been spent at most one time + transactions = list(response) if transactions: - if len(transactions) > 1: + if len(transactions) != 1: raise Exception('`{}` was spent more then once. There is a problem with the chain'.format( txid)) else: @@ -364,11 +352,12 @@ class Bigchain(object): raise exceptions.TransactionOwnerError('current_owner `{}` does not own the input `{}`'.format( transaction['transaction']['current_owner'], transaction['transaction']['input'])) - # check if the input was already spent + # check if the input was already spent by a transaction other then this one. spent = self.get_spent(tx_input['id']) if spent: - raise exceptions.DoubleSpend('input `{}` was already spent'.format( - transaction['transaction']['input'])) + if spent['id'] != transaction['id']: + raise exceptions.DoubleSpend('input `{}` was already spent'.format( + transaction['transaction']['input'])) # Check hash of the transaction calculated_hash = hash_data(self.serialize(transaction['transaction'])) @@ -508,13 +497,11 @@ class Bigchain(object): # 2. create the block with one transaction # 3. write the block to the bigchain - blocks_count = r.table('bigchain').count().run(self.conn) if blocks_count: raise GenesisBlockAlreadyExistsError('Cannot create the Genesis block') - payload = {'message': 'Hello World from the Bigchain'} transaction = self.create_transaction(self.me, self.me, None, 'GENESIS', payload=payload) transaction_signed = self.sign_transaction(transaction, self.me_private) diff --git a/tests/db/test_bigchain_api.py b/tests/db/test_bigchain_api.py index edea3a9e..f956f1e2 100644 --- a/tests/db/test_bigchain_api.py +++ b/tests/db/test_bigchain_api.py @@ -45,6 +45,7 @@ def inputs(user_public_key): def test_remove_unclosed_sockets(): pass + class TestBigchainApi(object): def test_create_transaction(self, b): @@ -58,7 +59,6 @@ class TestBigchainApi(object): with pytest.raises(TypeError): b.create_transaction('a', 'b', 'c', 'd', payload=[]) - def test_transaction_hash(self, b): payload = {'cats': 'are awesome'} tx = b.create_transaction('a', 'b', 'c', 'd', payload) @@ -166,8 +166,8 @@ class TestBigchainApi(object): b.create_genesis_block() genesis_blocks = list(r.table('bigchain') - .filter(r.row['block_number'] == 0) - .run(b.conn)) + .filter(r.row['block_number'] == 0) + .run(b.conn)) assert len(genesis_blocks) == 1 @@ -349,6 +349,24 @@ class TestTransactionValidation(object): assert tx_valid_signed == b.validate_transaction(tx_valid_signed) assert tx_valid_signed == b.is_valid_transaction(tx_valid_signed) + @pytest.mark.usefixtures('inputs') + def test_valid_non_create_transaction_after_block_creation(self, b, user_public_key, user_private_key): + input_valid = b.get_owned_ids(user_public_key).pop() + tx_valid = b.create_transaction(user_public_key, 'b', input_valid, 'd') + + tx_valid_signed = b.sign_transaction(tx_valid, user_private_key) + assert tx_valid_signed == b.validate_transaction(tx_valid_signed) + assert tx_valid_signed == b.is_valid_transaction(tx_valid_signed) + + # create block + block = b.create_block([tx_valid_signed]) + assert b.is_valid_block(block) + b.write_block(block, durability='hard') + + # check that the transaction is still valid after being written to the bigchain + assert tx_valid_signed == b.validate_transaction(tx_valid_signed) + assert tx_valid_signed == b.is_valid_transaction(tx_valid_signed) + class TestBlockValidation(object): @@ -357,7 +375,7 @@ class TestBlockValidation(object): # change block hash block.update({'id': 'abc'}) - with pytest.raises(exceptions.InvalidHash) as excinfo: + with pytest.raises(exceptions.InvalidHash): b.validate_block(block) @pytest.mark.skipif(reason='Separated tx validation from block creation.') @@ -368,7 +386,6 @@ class TestBlockValidation(object): tx_invalid = b.create_transaction('a', 'b', valid_input, 'c') block = b.create_block([tx_invalid]) - assert invalid_transactions == [tx_invalid] # create a block with invalid transactions block = { @@ -520,7 +537,6 @@ class TestBigchainVoter(object): time.sleep(1) voter.kill() - # retrive block from bigchain bigchain_block = r.table('bigchain').get(block['id']).run(b.conn) @@ -765,4 +781,3 @@ class TestBigchainBlock(object): def test_duplicated_transactions(self): pytest.skip('We may have duplicates in the initial_results and changefeed') - diff --git a/tests/db/test_voter.py b/tests/db/test_voter.py index 30ae8091..6800ce1c 100644 --- a/tests/db/test_voter.py +++ b/tests/db/test_voter.py @@ -3,7 +3,6 @@ import time import rethinkdb as r import multiprocessing as mp -from bigchaindb import Bigchain from bigchaindb.voter import Voter, BlockStream from bigchaindb.crypto import PublicKey @@ -35,7 +34,6 @@ class TestBigchainVoter(object): .order_by(r.asc((r.row['block']['timestamp']))) .run(b.conn)) - # validate vote assert len(blocks[1]['votes']) == 1 vote = blocks[1]['votes'][0] @@ -78,7 +76,6 @@ class TestBigchainVoter(object): .order_by(r.asc((r.row['block']['timestamp']))) .run(b.conn)) - # validate vote assert len(blocks[1]['votes']) == 1 vote = blocks[1]['votes'][0] @@ -93,7 +90,7 @@ class TestBigchainVoter(object): def test_valid_block_voting_with_transfer_transactions(self, b): q_new_block = mp.Queue() - genesis = b.create_genesis_block() + b.create_genesis_block() # create a `CREATE` transaction test_user_priv, test_user_pub = b.generate_keys() @@ -121,11 +118,9 @@ class TestBigchainVoter(object): .order_by(r.asc((r.row['block']['timestamp']))) .run(b.conn)) - # validate vote assert len(blocks[1]['votes']) == 1 - # create a `TRANSFER` transaction test_user2_priv, test_user2_pub = b.generate_keys() tx2 = b.create_transaction(test_user_pub, test_user2_pub, tx['id'], 'TRANSFER') @@ -152,7 +147,6 @@ class TestBigchainVoter(object): .order_by(r.asc((r.row['block']['timestamp']))) .run(b.conn)) - # validate vote assert len(blocks[2]['votes']) == 1 @@ -182,7 +176,6 @@ class TestBigchainVoter(object): assert not b.is_valid_block(block) b.write_block(block, durability='hard') - # vote voter.start() time.sleep(1) @@ -283,7 +276,6 @@ class TestBigchainVoter(object): time.sleep(1) voter.kill() - # retrive blocks from bigchain blocks = list(r.table('bigchain') .order_by(r.asc((r.row['block']['timestamp']))) @@ -303,7 +295,6 @@ class TestBigchainVoter(object): pass - class TestBlockStream(object): def test_if_federation_size_is_greater_than_one_ignore_past_blocks(self, b): @@ -335,7 +326,6 @@ class TestBlockStream(object): assert bs.get() == block_1 assert bs.get() == block_2 - def test_if_old_blocks_get_should_return_old_block_first(self, b): # create two blocks block_1 = b.create_block([]) From 6c75a5bb6d1a07013d6313eed6c297b8fc1ad0d6 Mon Sep 17 00:00:00 2001 From: Rodolphe Marques Date: Tue, 23 Feb 2016 13:54:54 +0100 Subject: [PATCH 19/44] removed exception print --- bigchaindb/core.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bigchaindb/core.py b/bigchaindb/core.py index ce3c5bb7..d2c7b4ad 100644 --- a/bigchaindb/core.py +++ b/bigchaindb/core.py @@ -466,8 +466,7 @@ class Bigchain(object): try: self.validate_block(block) return True - except Exception as e: - print(e) + except Exception: return False def write_block(self, block, durability='soft'): From 8ae779f2361cf5af4f1b73ecf81b4217514fe42c Mon Sep 17 00:00:00 2001 From: Greg McMullen Date: Tue, 23 Feb 2016 15:26:11 +0100 Subject: [PATCH 20/44] Added protection for discrimination based on species Protects Wrigley! --- CODE_OF_CONDUCT.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index b27f5c96..b5beacc0 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -7,7 +7,7 @@ contribute to the project. We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, -body size, race, ethnicity, age, religion, or nationality. +body size, race, ethnicity, age, religion, nationality, or species--no picking on Wrigley for being a buffalo! Examples of unacceptable behavior by participants include: From 593faaeb1977af160dce66728b94c93e45486d6d Mon Sep 17 00:00:00 2001 From: troymc Date: Tue, 23 Feb 2016 15:31:53 +0100 Subject: [PATCH 21/44] Simplify README.md to four sets of links --- README.md | 93 ++++++++++++++----------------------------------------- 1 file changed, 24 insertions(+), 69 deletions(-) diff --git a/README.md b/README.md index 948b879c..86a198e0 100644 --- a/README.md +++ b/README.md @@ -1,81 +1,36 @@ # BigchainDB +A scalable blockchain database. [The whitepaper](https://www.bigchaindb.com/whitepaper/) explains what that means. + [![Join the chat at https://gitter.im/bigchaindb/bigchaindb](https://badges.gitter.im/bigchaindb/bigchaindb.svg)](https://gitter.im/bigchaindb/bigchaindb?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![PyPI](https://img.shields.io/pypi/v/bigchaindb.svg)](https://pypi.python.org/pypi/BigchainDB) [![Travis branch](https://img.shields.io/travis/bigchaindb/bigchaindb/develop.svg)](https://travis-ci.org/bigchaindb/bigchaindb) [![Codecov branch](https://img.shields.io/codecov/c/github/bigchaindb/bigchaindb/develop.svg)](https://codecov.io/github/bigchaindb/bigchaindb?branch=develop) [![Documentation Status](https://readthedocs.org/projects/bigchaindb/badge/?version=develop)](http://bigchaindb.readthedocs.org/en/develop/?badge=develop) -## Documentation +## Quick Start -Documentation is available at https://bigchaindb.readthedocs.org/ +### [Install & Run BigchainDB](http://bigchaindb.readthedocs.org/en/develop/installing.html) +### [Run BigchainDB with Docker](http://bigchaindb.readthedocs.org/en/develop/installing.html#run-bigchaindb-with-docker) +### [Getting Started (Tutorial)](http://bigchaindb.readthedocs.org/en/develop/getting-started.html) -## Getting started +## Links for Everyone +* [BigchainDB.com](https://www.bigchaindb.com/) - the main BigchainDB website, including newsletter signup +* [Whitepaper](https://www.bigchaindb.com/whitepaper/) - outlines the motivations, goals and core algorithms of BigchainDB +* [Roadmap](ROADMAP.md) +* [Blog](https://medium.com/the-bigchaindb-blog) +* [Twitter](https://twitter.com/BigchainDB) +* [Google Group](https://groups.google.com/forum/#!forum/bigchaindb) -### Install RethinkDB +## Links for Developers +* [Documentation](http://bigchaindb.readthedocs.org/en/develop/#) - for developers +* [CONTRIBUTING.md](CONTRIBUTING.md) - how to contribute +* [Community guidelines](CODE_OF_CONDUCT.md) +* [Open issues](https://github.com/bigchaindb/bigchaindb/issues) +* [Open pull requests](https://github.com/bigchaindb/bigchaindb/pulls) +* [Gitter chatroom](https://gitter.im/bigchaindb/bigchaindb) -#### On Ubuntu -```sh -# install rethinkdb https://rethinkdb.com/docs/install/ubuntu/ -$ source /etc/lsb-release && echo "deb http://download.rethinkdb.com/apt $DISTRIB_CODENAME main" | sudo tee /etc/apt/sources.list.d/rethinkdb.list -$ wget -qO- http://download.rethinkdb.com/apt/pubkey.gpg | sudo apt-key add - -$ sudo apt-get update -$ sudo apt-get install rethinkdb - -# start rethinkdb -$ rethinkdb -``` - -#### On other platforms -To install RethinkDB on other platform, please refer to [the official documentation](https://rethinkdb.com/docs/install/). - -### Install BigchainDB -```sh -$ pip install bigchaindb -``` - -### Running BigchainDB -Currently BigchainDB only supports Python 3.4+ - - -Start the main process. If it's the first time `bigchaindb` will generate a default -configuration file for you. -```sh -$ bigchaindb start -``` - -Generate some tests transactions: - -```sh -$ bigchaindb-benchmark load # add '-m' if you want to use all your cores -``` - -To know more about the bigchain command run -```sh -$ bigchaindb -h -``` - -#### Importing `BigchainDB` from the interpreter (python/ipython) -Make sure your `rethinkdb` process is running. - -```python ->>> from bigchaindb import Bigchain ->>> b = Bigchain() ->>> b.me -'2B8C8PJxhycFzn4wncRhBNmMWwE5Frr9nLBUa1dGGxj5W' -``` - -#### Configuration - -BigchainDB creates a default configuration file on `$HOME/.bigchaindb` on the -first run. - -```sh -$ bigchaindb show-config -``` - -#### Testing - -``` -$ py.test -v -``` +## Legal +* [Licenses](LICENSES.md) - open source & open content +* [Imprint](https://www.bigchaindb.com/imprint/) +* [Contact Us](https://www.bigchaindb.com/contact/) From 5bc591c6c40c7965b48cf261ae4da5199305c2b8 Mon Sep 17 00:00:00 2001 From: ryan Date: Tue, 23 Feb 2016 16:14:27 +0100 Subject: [PATCH 22/44] reorganize documentation --- README.md | 17 ----------------- docs/source/index.rst | 1 + docs/source/monitoring.md | 16 ++++++++++++++++ 3 files changed, 17 insertions(+), 17 deletions(-) create mode 100644 docs/source/monitoring.md diff --git a/README.md b/README.md index bd1a06e6..948b879c 100644 --- a/README.md +++ b/README.md @@ -79,20 +79,3 @@ $ bigchaindb show-config ``` $ py.test -v ``` - -#### Monitoring - -BigchainDB uses [statsd](https://github.com/etsy/statsd) for monitoring. To fully take advantage of this functionality requires some additional infrastructure: an agent to listen for metrics (e.g. [telegraf](https://github.com/influxdata/telegraf)), a time-series database (e.g. [influxdb](https://influxdata.com/time-series-platform/influxdb/), and a frontend to display analytics (e.g. [Grafana](http://grafana.org/)). - -For ease of use, we've provided a docker compose file that sets up all these services for testing. Simply run in the BigchainDB directory: - -```sh -$ docker-compose -f docker-compose-monitor.yml build -$ docker-compose -f docker-compose-monitor.yml up -``` - -and point a browser tab to `http://localhost:3000/dashboard/script/bigchaindb_dashboard.js`. Login and password are `admin` by default. If BigchainDB is running and processing transactions, you should see analytics—if not, start BigchainDB as above, and refresh the page after a few seconds. - -If you're not interested in monitoring, don't worry: BigchainDB will function just fine without any monitoring setup. - -Feel free to modify the [custom Grafana dashboard](https://github.com/rhsimplex/grafana-bigchaindb-docker/blob/master/bigchaindb_dashboard.js) to your liking! \ No newline at end of file diff --git a/docs/source/index.rst b/docs/source/index.rst index f8ef2a32..d15ff99b 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -21,6 +21,7 @@ Table of Contents models json-serialization developer-interface + monitoring contributing faq release-notes diff --git a/docs/source/monitoring.md b/docs/source/monitoring.md new file mode 100644 index 00000000..7fee3620 --- /dev/null +++ b/docs/source/monitoring.md @@ -0,0 +1,16 @@ +# Monitoring + +BigchainDB uses [statsd](https://github.com/etsy/statsd) for monitoring. To fully take advantage of this functionality requires some additional infrastructure: an agent to listen for metrics (e.g. [telegraf](https://github.com/influxdata/telegraf)), a time-series database (e.g. [influxdb](https://influxdata.com/time-series-platform/influxdb/), and a frontend to display analytics (e.g. [Grafana](http://grafana.org/)). + +For ease of use, we've provided a docker compose file that sets up all these services for testing. Simply run in the BigchainDB directory: + +```text +$ docker-compose -f docker-compose-monitor.yml build +$ docker-compose -f docker-compose-monitor.yml up +``` + +and point a browser tab to `http://localhost:3000/dashboard/script/bigchaindb_dashboard.js`. Login and password are `admin` by default. If BigchainDB is running and processing transactions, you should see analytics—if not, start BigchainDB as above, and refresh the page after a few seconds. + +If you're not interested in monitoring, don't worry: BigchainDB will function just fine without any monitoring setup. + +Feel free to modify the [custom Grafana dashboard](https://github.com/rhsimplex/grafana-bigchaindb-docker/blob/master/bigchaindb_dashboard.js) to your liking! \ No newline at end of file From a466cfbd6cd533db618aac6e8a2861fe7624b896 Mon Sep 17 00:00:00 2001 From: Greg McMullen Date: Tue, 23 Feb 2016 16:40:26 +0100 Subject: [PATCH 23/44] Bigchain -> BigchainDB in only one place and not other places. --- bigchaindb/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigchaindb/core.py b/bigchaindb/core.py index d2c7b4ad..9706253f 100644 --- a/bigchaindb/core.py +++ b/bigchaindb/core.py @@ -501,7 +501,7 @@ class Bigchain(object): if blocks_count: raise GenesisBlockAlreadyExistsError('Cannot create the Genesis block') - payload = {'message': 'Hello World from the Bigchain'} + payload = {'message': 'Hello World from the BigchainDB'} transaction = self.create_transaction(self.me, self.me, None, 'GENESIS', payload=payload) transaction_signed = self.sign_transaction(transaction, self.me_private) From be72aafd949d5734dda2ac03df38f53eb528ee6e Mon Sep 17 00:00:00 2001 From: ryan Date: Tue, 23 Feb 2016 17:08:07 +0100 Subject: [PATCH 24/44] documentation links --- docs/source/monitoring.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/monitoring.md b/docs/source/monitoring.md index 7fee3620..db794a62 100644 --- a/docs/source/monitoring.md +++ b/docs/source/monitoring.md @@ -9,7 +9,7 @@ $ docker-compose -f docker-compose-monitor.yml build $ docker-compose -f docker-compose-monitor.yml up ``` -and point a browser tab to `http://localhost:3000/dashboard/script/bigchaindb_dashboard.js`. Login and password are `admin` by default. If BigchainDB is running and processing transactions, you should see analytics—if not, start BigchainDB as above, and refresh the page after a few seconds. +and point a browser tab to `http://localhost:3000/dashboard/script/bigchaindb_dashboard.js`. Login and password are `admin` by default. If BigchainDB is running and processing transactions, you should see analytics—if not, [start BigchainDB](installing.html#run-bigchaindb) and refresh the page after a few seconds. If you're not interested in monitoring, don't worry: BigchainDB will function just fine without any monitoring setup. From 65d18211fb417b64190c70dc92105b7c83f9610d Mon Sep 17 00:00:00 2001 From: ryan Date: Tue, 23 Feb 2016 17:52:07 +0100 Subject: [PATCH 25/44] pseudo TTY --- docker-compose-monitor.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docker-compose-monitor.yml b/docker-compose-monitor.yml index ea4faf54..ee7fe3a4 100644 --- a/docker-compose-monitor.yml +++ b/docker-compose-monitor.yml @@ -11,6 +11,7 @@ influxdb: grafana: image: rhsimplex/grafana-bigchaindb-docker + tty: true ports: - "3000:3000" links: From 39e260f88930b2f95a07f57f7e35314167b12ba9 Mon Sep 17 00:00:00 2001 From: ryan Date: Tue, 23 Feb 2016 18:38:57 +0100 Subject: [PATCH 26/44] put queue gauge in correct place --- bigchaindb/block.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigchaindb/block.py b/bigchaindb/block.py index 11b6b7ed..74d969fd 100644 --- a/bigchaindb/block.py +++ b/bigchaindb/block.py @@ -37,7 +37,6 @@ class Block(object): b = Bigchain() while True: - c.gauge('tx_queue_gauge', self.q_tx_to_validate.qsize(), rate=bigchaindb.config['statsd']['rate']) tx = self.q_new_transaction.get() # poison pill @@ -58,6 +57,7 @@ class Block(object): b = Bigchain() while True: + c.gauge('tx_queue_gauge', self.q_tx_to_validate.qsize(), rate=bigchaindb.config['statsd']['rate']) tx = self.q_tx_to_validate.get() # poison pill From 8fd45048a48e5983c9ec20472a34cdbe196f7d27 Mon Sep 17 00:00:00 2001 From: ryan Date: Wed, 24 Feb 2016 11:17:34 +0100 Subject: [PATCH 27/44] add loading text to monitoring documentation --- docs/source/monitoring.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/source/monitoring.md b/docs/source/monitoring.md index db794a62..436a42aa 100644 --- a/docs/source/monitoring.md +++ b/docs/source/monitoring.md @@ -1,6 +1,6 @@ # Monitoring -BigchainDB uses [statsd](https://github.com/etsy/statsd) for monitoring. To fully take advantage of this functionality requires some additional infrastructure: an agent to listen for metrics (e.g. [telegraf](https://github.com/influxdata/telegraf)), a time-series database (e.g. [influxdb](https://influxdata.com/time-series-platform/influxdb/), and a frontend to display analytics (e.g. [Grafana](http://grafana.org/)). +BigchainDB uses [statsd](https://github.com/etsy/statsd) for monitoring. To fully take advantage of this functionality requires some additional infrastructure: an agent to listen for metrics (e.g. [telegraf](https://github.com/influxdata/telegraf)), a time-series database (e.g. [influxdb](https://influxdata.com/time-series-platform/influxdb/)), and a frontend to display analytics (e.g. [Grafana](http://grafana.org/)). For ease of use, we've provided a docker compose file that sets up all these services for testing. Simply run in the BigchainDB directory: @@ -9,7 +9,13 @@ $ docker-compose -f docker-compose-monitor.yml build $ docker-compose -f docker-compose-monitor.yml up ``` -and point a browser tab to `http://localhost:3000/dashboard/script/bigchaindb_dashboard.js`. Login and password are `admin` by default. If BigchainDB is running and processing transactions, you should see analytics—if not, [start BigchainDB](installing.html#run-bigchaindb) and refresh the page after a few seconds. +and point a browser tab to `http://localhost:3000/dashboard/script/bigchaindb_dashboard.js`. Login and password are `admin` by default. If BigchainDB is running and processing transactions, you should see analytics—if not, [start BigchainDB](installing.html#run-bigchaindb) and load some test transactions: + +```text +$ bigchaindb-benchmark load +``` + +and refresh the page after a few seconds. If you're not interested in monitoring, don't worry: BigchainDB will function just fine without any monitoring setup. From 43b67b83183c4873d846ee8a95fca6b79fc25b1f Mon Sep 17 00:00:00 2001 From: ryan Date: Wed, 24 Feb 2016 11:43:57 +0100 Subject: [PATCH 28/44] more readable syntax --- bigchaindb/monitor.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bigchaindb/monitor.py b/bigchaindb/monitor.py index be5c27cd..9094fea3 100644 --- a/bigchaindb/monitor.py +++ b/bigchaindb/monitor.py @@ -21,10 +21,10 @@ class Monitor(statsd.StatsClient): kwargs = {} # set prefix, parameters from configuration file - if not kwargs.get('prefix'): + if 'prefix' not in kwargs: kwargs['prefix'] = '{hostname}.'.format(hostname=node()) - if not kwargs.get('host'): + if 'host' not in kwargs: kwargs['host'] = bigchaindb.config['statsd']['host'] - if not kwargs.get('port'): + if 'port' not in kwargs: kwargs['port'] = bigchaindb.config['statsd']['port'] super().__init__(*args, **kwargs) From 667c9a22239d1ba3b4c537ca4a31525ddc8ac5ab Mon Sep 17 00:00:00 2001 From: ryan Date: Wed, 24 Feb 2016 11:51:17 +0100 Subject: [PATCH 29/44] readability refactor --- bigchaindb/block.py | 4 ++-- bigchaindb/core.py | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/bigchaindb/block.py b/bigchaindb/block.py index 74d969fd..0b021969 100644 --- a/bigchaindb/block.py +++ b/bigchaindb/block.py @@ -12,7 +12,7 @@ from bigchaindb.monitor import Monitor logger = logging.getLogger(__name__) -c = Monitor() +monitor = Monitor() class Block(object): @@ -57,7 +57,7 @@ class Block(object): b = Bigchain() while True: - c.gauge('tx_queue_gauge', self.q_tx_to_validate.qsize(), rate=bigchaindb.config['statsd']['rate']) + monitor.gauge('tx_queue_gauge', self.q_tx_to_validate.qsize(), rate=bigchaindb.config['statsd']['rate']) tx = self.q_tx_to_validate.get() # poison pill diff --git a/bigchaindb/core.py b/bigchaindb/core.py index 8a962a1b..e4074112 100644 --- a/bigchaindb/core.py +++ b/bigchaindb/core.py @@ -12,7 +12,7 @@ from bigchaindb import exceptions from bigchaindb.crypto import hash_data, PublicKey, PrivateKey, generate_key_pair from bigchaindb.monitor import Monitor -c = Monitor() +monitor = Monitor() class GenesisBlockAlreadyExistsError(Exception): @@ -71,7 +71,7 @@ class Bigchain(object): def reconnect(self): return r.connect(host=self.host, port=self.port, db=self.dbname) - @c.timer('create_transaction', rate=bigchaindb.config['statsd']['rate']) + @monitor.timer('create_transaction', rate=bigchaindb.config['statsd']['rate']) def create_transaction(self, current_owner, new_owner, tx_input, operation, payload=None): """Create a new transaction @@ -181,7 +181,7 @@ class Bigchain(object): public_key = PublicKey(public_key_base58) return public_key.verify(self.serialize(data), signature) - @c.timer('write_transaction', rate=bigchaindb.config['statsd']['rate']) + @monitor.timer('write_transaction', rate=bigchaindb.config['statsd']['rate']) def write_transaction(self, signed_transaction): """Write the transaction to bigchain. @@ -318,7 +318,7 @@ class Bigchain(object): return owned - @c.timer('validate_transaction', rate=bigchaindb.config['statsd']['rate']) + @monitor.timer('validate_transaction', rate=bigchaindb.config['statsd']['rate']) def validate_transaction(self, transaction): """Validate a transaction. @@ -431,7 +431,7 @@ class Bigchain(object): return block - @c.timer('validate_block') + @monitor.timer('validate_block') # TODO: check that the votings structure is correctly constructed def validate_block(self, block): """Validate a block. @@ -476,7 +476,7 @@ class Bigchain(object): except Exception: return False - @c.timer('write_block') + @monitor.timer('write_block') def write_block(self, block, durability='soft'): """Write a block to bigchain. From de24260c2bb2e5e99939a28348e074ea7fd4388b Mon Sep 17 00:00:00 2001 From: troymc Date: Thu, 25 Feb 2016 09:07:57 +0100 Subject: [PATCH 30/44] add diagram to monitoring section of docs --- .../_static/monitoring_system_diagram.png | Bin 0 -> 43931 bytes docs/source/monitoring.md | 18 ++++++++++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) create mode 100644 docs/source/_static/monitoring_system_diagram.png diff --git a/docs/source/_static/monitoring_system_diagram.png b/docs/source/_static/monitoring_system_diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..6b654eac95ae0291c26333597d2341b74089df4f GIT binary patch literal 43931 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 U(z8LA0|Pwdq?9Bp#ErlI50G6q#Q*>R literal 0 HcmV?d00001 diff --git a/docs/source/monitoring.md b/docs/source/monitoring.md index 436a42aa..4d61cbc4 100644 --- a/docs/source/monitoring.md +++ b/docs/source/monitoring.md @@ -1,21 +1,31 @@ # Monitoring -BigchainDB uses [statsd](https://github.com/etsy/statsd) for monitoring. To fully take advantage of this functionality requires some additional infrastructure: an agent to listen for metrics (e.g. [telegraf](https://github.com/influxdata/telegraf)), a time-series database (e.g. [influxdb](https://influxdata.com/time-series-platform/influxdb/)), and a frontend to display analytics (e.g. [Grafana](http://grafana.org/)). +BigchainDB uses [StatsD](https://github.com/etsy/statsd) for monitoring. We require some additional infrastructure to take full advantage of its functionality: -For ease of use, we've provided a docker compose file that sets up all these services for testing. Simply run in the BigchainDB directory: +* an agent to listen for metrics: [Telegraf](https://github.com/influxdata/telegraf), +* a time-series database: [InfluxDB](https://influxdata.com/time-series-platform/influxdb/), and +* a frontend to display analytics: [Grafana](http://grafana.org/). +We put each of those inside its own Docker container. The whole system is illustrated below. + +![BigchainDB monitoring system diagram: Application metrics flow from servers running BigchainDB to Telegraf to InfluxDB to Grafana](./_static/monitoring_system_diagram.png) + +For ease of use, we've created a Docker [_Compose file_](https://docs.docker.com/compose/compose-file/) (named `docker-compose-monitor.yml`) to define the monitoring system setup. To use it, just go to to the top `bigchaindb` directory and run: ```text $ docker-compose -f docker-compose-monitor.yml build $ docker-compose -f docker-compose-monitor.yml up ``` -and point a browser tab to `http://localhost:3000/dashboard/script/bigchaindb_dashboard.js`. Login and password are `admin` by default. If BigchainDB is running and processing transactions, you should see analytics—if not, [start BigchainDB](installing.html#run-bigchaindb) and load some test transactions: +then point a browser tab to: +[http://localhost:3000/dashboard/script/bigchaindb_dashboard.js](http://localhost:3000/dashboard/script/bigchaindb_dashboard.js) + +The login and password are `admin` by default. If BigchainDB is running and processing transactions, you should see analytics—if not, [start BigchainDB](installing.html#run-bigchaindb) and load some test transactions: ```text $ bigchaindb-benchmark load ``` -and refresh the page after a few seconds. +then refresh the page after a few seconds. If you're not interested in monitoring, don't worry: BigchainDB will function just fine without any monitoring setup. From 5fd12a8b3f49eee48bfb385666a25e7fa246b04f Mon Sep 17 00:00:00 2001 From: troymc Date: Thu, 25 Feb 2016 16:18:51 +0100 Subject: [PATCH 31/44] Add Python Style Guide, update CONTRIBUTING.md --- CONTRIBUTING.md | 28 ++++++++------- PYTHON_STYLE_GUIDE.md | 79 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+), 13 deletions(-) create mode 100644 PYTHON_STYLE_GUIDE.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c0975b0c..8e3a517b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,8 +18,8 @@ To contribute code or documentation, you need a [GitHub account](https://github. Familiarize yourself with how we do coding and documentation in the BigchainDB project, including: -* our Python Style Guide (coming soon) -* [our documentation strategy](./docs/README.md) (including code documentation) +* [our Python Style Guide](PYTHON_STYLE_GUIDE.md) (includes how we write tests) +* [our documentation strategy](./docs/README.md) (including in-code documentation) * the GitHub Flow (workflow) * [GitHub Guide: Understanding the GitHub Flow](https://guides.github.com/introduction/flow/) * [Scott Chacon's blog post about GitHub Flow](http://scottchacon.com/2011/08/31/github-flow.html) @@ -63,27 +63,29 @@ git checkout -b new-branch-name ### Step 5 - Make Edits, git add, git commit -With your new branch checked out locally, make changes or additions to the code or documentation, git add them, and git commit them. +With your new branch checked out locally, make changes or additions to the code or documentation. Remember to: + +* follow [our Python Style Guide](PYTHON_STYLE_GUIDE.md). +* write and run tests for any new or changed code. There's a section in [our Python Style Guide](PYTHON_STYLE_GUIDE.md) about writing and running tests. +* add or update documentation as necessary. Follow [our documentation strategy](./docs/README.md). + +As you go, git add and git commit your changes or additions, e.g. ```text -git add new-or-changed-file +git add new-or-changed-file-1 +git add new-or-changed-file-2 git commit -m "Short description of new or changed things" ``` -Remember to write tests for new code. If you don't, our code (test) coverage will go down, and we won't be able to accept your code. (We have some hard checks that run on all new pull requests and code coverage is one of them.) - -Please run all existing tests to make sure you didn't break something. Do: -```text -py.test -v -``` - -Remember to write or modify documentation to reflect your additions or changes. - You will want to merge changes from upstream (i.e. the original repository) into your new branch from time to time, using something like: ```text git fetch upstream git merge upstream/develop ``` +Once you're done commiting a set of new things and you're ready to submit them for inclusion, please be sure to run all the tests (as per the instructions at the end of our [Python Style Guide](PYTHON_STYLE_GUIDE.md)). + +(When you submit your pull request [following the instructions below], we run all the tests automatically, so we will see if some are failing. If you don't know why some tests are failing, you can still submit your pull request, but be sure to note the failing tests and to ask for help with resolving them.) + ### Step 6 - Push Your New Branch to origin Make sure you've commited all the additions or changes you want to include in your pull request. Then push your new branch to origin (i.e. _your_ remote bigchaindb repository). diff --git a/PYTHON_STYLE_GUIDE.md b/PYTHON_STYLE_GUIDE.md new file mode 100644 index 00000000..9d23e0c0 --- /dev/null +++ b/PYTHON_STYLE_GUIDE.md @@ -0,0 +1,79 @@ +# Python Style Guide + +This guide starts out with our general Python coding style guidelines and ends with a section on how we write & run (Python) tests. + +## General Python Coding Style Guidelines + +Our starting point is [PEP8](https://www.python.org/dev/peps/pep-0008/), the standard "Style Guide for Python Code." Many Python IDEs will check your code against PEP8. (Note that PEP8 isn't frozen; it actually changes over time, but slowly.) + +BigchainDB uses Python 3.4+, so you can ignore all PEP8 guidelines specific to Python 2. + +### Python Docstrings + +PEP8 says some things about docstrings, but not what to put in them or how to structure them. [PEP257](https://www.python.org/dev/peps/pep-0257/) was one proposal for docstring conventions, but we prefer [Google-style docstrings](https://google.github.io/styleguide/pyguide.html?showone=Comments#Comments) instead: they're easier to read and the [napoleon extension](http://www.sphinx-doc.org/en/stable/ext/napoleon.html) for Sphinx lets us turn them into nice-looking documentation. Here are some references on Google-style docstrings: + +* [Google's docs on Google-style docstrings](https://google.github.io/styleguide/pyguide.html?showone=Comments#Comments) +* [napoleon's docs include an overview of Google-style docstrings](http://sphinxcontrib-napoleon.readthedocs.org/en/latest/index.html) +* [Example Google-style docstrings](http://sphinxcontrib-napoleon.readthedocs.org/en/latest/example_google.html) (from napoleon's docs) + +### Maximum Line Length + +PEP8 has some [maximum line length guidelines](https://www.python.org/dev/peps/pep-0008/#id17), starting with "Limit all lines to a maximum of 79 characters" but "for flowing long blocks of text with fewer structural restrictions (docstrings or comments), the line length should be limited to 72 characters." + +We discussed this at length, and it seems that the consensus is: try to keep line lengths less than 79/72 characters, unless you have a special situation where longer lines would improve readability. (The basic reason is that 79/72 works for everyone, and BigchainDB is an open source project.) + +### Single or Double Quotes? + +Python lets you use single or double quotes. PEP8 says you can use either, as long as you're consistent. We try to stick to using single quotes, except in cases where using double quotes is more readable. For example: +```python +print('This doesn\'t look so nice.') +print("Doesn't this look nicer?") +``` + +### Breaking Strings Across Multiple Lines + +Should we use parentheses or slashes (`\`) to break strings across multiple lines, i.e. +```python +my_string = ('This is a very long string, so long that it will not fit into just one line ' + 'so it must be split across multiple lines.') +# or +my_string = 'This is a very long string, so long that it will not fit into just one line ' \ + 'so it must be split across multiple lines.' +``` + +It seems the preference is for slashes, but using parentheses is okay too. (There are good arguments either way. Arguing about it seems like a waste of time.) + +### Using the % operator or `format()` to Format Strings + +Given the choice: +```python +x = 'name: %s; score: %d' % (name, n) +# or +x = 'name: {}; score: {}'.format(name, n) +``` + +we use the `format()` version. It's newer and doesn't require you to remember a bunch of arcane markup. + + +## Writing (Python) Tests + +We write tests for our Python code using the [pytest](http://pytest.org/latest/) framework. + +All tests go in the `bigchaindb/tests` directory or one of its subdirectories. You can use the tests already in there as templates or examples. + +To run all the tests, first make sure you have RethinkDB running: +```text +$ rethinkdb +``` + +then in another terminal, do: +```text +$ py.test -v +``` + +If that doesn't work (e.g. maybe you are running in a conda virtual environment), try: +```text +$ python -m pytest -v +``` + +We use [Travis CI](https://travis-ci.com/), so that whenever someone creates a new BigchainDB pull request on GitHub, Travis CI gets the new code and does _a bunch of stuff_. You can find out what we tell Travis CI to do in [the `.travis.yml` file](.travis.yml): it tells Travis CI how to install BigchainDB, how to run all the tests, and what to do "after success" (e.g. run `codecov`). (We use [Codecov](https://codecov.io/) to get a rough estimate of our test coverage.) From 2a611c7d3250057d5afbd5d7a33fff26e1e55467 Mon Sep 17 00:00:00 2001 From: troymc Date: Thu, 25 Feb 2016 17:26:42 +0100 Subject: [PATCH 32/44] Make Sylvain's suggested changes to py style guide --- PYTHON_STYLE_GUIDE.md | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/PYTHON_STYLE_GUIDE.md b/PYTHON_STYLE_GUIDE.md index 9d23e0c0..d416ad65 100644 --- a/PYTHON_STYLE_GUIDE.md +++ b/PYTHON_STYLE_GUIDE.md @@ -52,7 +52,7 @@ x = 'name: %s; score: %d' % (name, n) x = 'name: {}; score: {}'.format(name, n) ``` -we use the `format()` version. It's newer and doesn't require you to remember a bunch of arcane markup. +we use the `format()` version. The [official Python documentation says](https://docs.python.org/2/library/stdtypes.html#str.format), "This method of string formatting is the new standard in Python 3, and should be preferred to the % formatting described in String Formatting Operations in new code." ## Writing (Python) Tests @@ -61,6 +61,8 @@ We write tests for our Python code using the [pytest](http://pytest.org/latest/) All tests go in the `bigchaindb/tests` directory or one of its subdirectories. You can use the tests already in there as templates or examples. +### Standard Ways to Run All Tests + To run all the tests, first make sure you have RethinkDB running: ```text $ rethinkdb @@ -76,4 +78,25 @@ If that doesn't work (e.g. maybe you are running in a conda virtual environment) $ python -m pytest -v ``` +You can also run all tests via `setup.py`, using: +```text +$ python setup.py test +``` + +### Using `docker-compose` to Run the Tests + +You can use `docker-compose` to run the tests. (You don't have to start RethinkDB first: `docker-compose` does that on its own, when it reads the `docker-compose.yml` file.) + +First, build the images (~once), using: +```text +$ docker-compose build +``` + +then run the tests using: +```text +$ docker-compose run --rm bigchaindb py.test -v +``` + +### Automated Testing of All Pull Requests + We use [Travis CI](https://travis-ci.com/), so that whenever someone creates a new BigchainDB pull request on GitHub, Travis CI gets the new code and does _a bunch of stuff_. You can find out what we tell Travis CI to do in [the `.travis.yml` file](.travis.yml): it tells Travis CI how to install BigchainDB, how to run all the tests, and what to do "after success" (e.g. run `codecov`). (We use [Codecov](https://codecov.io/) to get a rough estimate of our test coverage.) From 24f8d7e5097dd64658180f93530d7398fd21d0e2 Mon Sep 17 00:00:00 2001 From: troymc Date: Sat, 27 Feb 2016 11:57:55 +0100 Subject: [PATCH 33/44] Changed Docker instructions slightly in docs --- docs/source/installing.md | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/docs/source/installing.md b/docs/source/installing.md index ff51539c..7cd451f3 100644 --- a/docs/source/installing.md +++ b/docs/source/installing.md @@ -91,7 +91,7 @@ For those who like using Docker and wish to experiment with BigchainDB in non-production environments, we currently maintain a `dockerfile` that can be used to build an image for `bigchaindb`, along with a `docker-compose.yml` file to manage a "standalone node", consisting mainly of two containers: one for -RethinkDB, and another for `bigchaindb`. +RethinkDB, and another for BigchainDB. Assuming you have `docker` and `docker-compose` installed, you would proceed as follows. @@ -112,27 +112,21 @@ stored on your host machine under ` ~/.bigchaindb_docker/config`: $ docker-compose run --rm bigchaindb bigchaindb configure ``` -You can load test transactions via: +You can then start it up (in the background, as a daemon) using: +```text +$ docker-compose up -d +``` + +then you can load test transactions via: ```text $ docker-compose run --rm bigchaindb bigchaindb-benchmark load ``` -You should then be able to start `bigchaindb`, via: -```text -$ docker-compose run --rm bigchaindb bigchaindb start -``` +If you're on Linux, you can probably view the RethinkDB dashboard at: -or -```text -$ docker-compose up -``` +[http://localhost:58080/](http://localhost:58080/) -You should be able to view the RethinkDB dashboard at: -```text -http://docker_host:58080/ -``` - -where `docker_host` is the IP or hostname of the machine running the Docker -engine. If you are developing on Linux, this most likely will be `localhost`, -whereas if you are running docker-machine (e.g.: on Mac OS X) this will be the +If that doesn't work, then replace `localhost` +with the IP or hostname of the machine running the Docker engine. +If you are running docker-machine (e.g.: on Mac OS X) this will be the IP of the Docker machine (`docker-machine ip machine_name`). From ffdd7cad61d9db4e204c44aa041e62905e991323 Mon Sep 17 00:00:00 2001 From: ryan Date: Mon, 29 Feb 2016 14:01:43 +0100 Subject: [PATCH 34/44] docker images now come from bigchaindb dockerhub account --- docker-compose-monitor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose-monitor.yml b/docker-compose-monitor.yml index ee7fe3a4..6865931d 100644 --- a/docker-compose-monitor.yml +++ b/docker-compose-monitor.yml @@ -10,7 +10,7 @@ influxdb: PRE_CREATE_DB: "telegraf" grafana: - image: rhsimplex/grafana-bigchaindb-docker + image: bigchaindb/grafana-bigchaindb-docker tty: true ports: - "3000:3000" @@ -18,7 +18,7 @@ grafana: - influxdb:localhost statsd: - image: rhsimplex/docker-telegraf-statsd + image: bigchaindb/docker-telegraf-statsd ports: - "8125:8125/udp" links: From d60062e7306131b9faa0dbd6eccf99061fc1db06 Mon Sep 17 00:00:00 2001 From: troymc Date: Mon, 29 Feb 2016 14:14:19 +0100 Subject: [PATCH 35/44] Improve install docs for non-Linux users --- docs/source/installing.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/source/installing.md b/docs/source/installing.md index 7cd451f3..fded0eaf 100644 --- a/docs/source/installing.md +++ b/docs/source/installing.md @@ -2,6 +2,12 @@ We're developing BigchainDB Server ("BigchainDB") on Ubuntu 14.04, but it should work on any OS that runs RethinkDB Server and Python 3.4+. (BigchainDB is built on top of RethinkDB Server.) +BigchainDB Server is intended to be run on each server in a large distributed cluster of servers; it's not very useful running by itself on a single computer. That's _why_ we're developing it on Ubuntu 14.04: it's one of the more common server operating systems. + +Mac OS X users may get the best results [running BigchainDB with Docker](https://bigchaindb.readthedocs.org/en/develop/installing.html#run-bigchaindb-with-docker). + +Windows users may get the best results [running BigchainDB in a VM with Vagrant](https://bigchaindb.readthedocs.org/en/develop/installing.html#how-to-install-bigchaindb-on-a-vm-with-vagrant). + ## Install and Run RethinkDB Server The RethinkDB documentation has instructions for how to install RethinkDB Server on a variety of operating systems. Do that (using their instructions for your OS): [Install RethinkDB Server](http://rethinkdb.com/docs/install/). From 752c5741e4d47a4496af9511429f55aae228ef35 Mon Sep 17 00:00:00 2001 From: troymc Date: Mon, 29 Feb 2016 14:22:42 +0100 Subject: [PATCH 36/44] Add comment on coming clients/drivers in docs --- docs/source/installing.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/source/installing.md b/docs/source/installing.md index fded0eaf..9ff24298 100644 --- a/docs/source/installing.md +++ b/docs/source/installing.md @@ -4,9 +4,11 @@ We're developing BigchainDB Server ("BigchainDB") on Ubuntu 14.04, but it should BigchainDB Server is intended to be run on each server in a large distributed cluster of servers; it's not very useful running by itself on a single computer. That's _why_ we're developing it on Ubuntu 14.04: it's one of the more common server operating systems. -Mac OS X users may get the best results [running BigchainDB with Docker](https://bigchaindb.readthedocs.org/en/develop/installing.html#run-bigchaindb-with-docker). +Mac OS X users may get the best results [running BigchainDB Server with Docker](https://bigchaindb.readthedocs.org/en/develop/installing.html#run-bigchaindb-with-docker). -Windows users may get the best results [running BigchainDB in a VM with Vagrant](https://bigchaindb.readthedocs.org/en/develop/installing.html#how-to-install-bigchaindb-on-a-vm-with-vagrant). +Windows users may get the best results [running BigchainDB Server in a VM with Vagrant](https://bigchaindb.readthedocs.org/en/develop/installing.html#how-to-install-bigchaindb-on-a-vm-with-vagrant). + +(Right now, there are no BigchainDB clients/drivers. Those will be able to run on a much larger array of operating systems. They're coming soon.) ## Install and Run RethinkDB Server From bd6e16b836af13a7f2cc2c6d926d42a7c200c63b Mon Sep 17 00:00:00 2001 From: vrde Date: Mon, 29 Feb 2016 14:28:02 +0100 Subject: [PATCH 37/44] Fix bug related to config overwrite --- bigchaindb/commands/bigchain.py | 2 +- tests/test_commands.py | 19 +++++++++++++++++++ tests/utils/test_config_utils.py | 2 +- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/bigchaindb/commands/bigchain.py b/bigchaindb/commands/bigchain.py index 33d37822..35b2de2f 100644 --- a/bigchaindb/commands/bigchain.py +++ b/bigchaindb/commands/bigchain.py @@ -45,7 +45,7 @@ def run_configure(args, skip_if_exists=False): if config_file_exists and not args.yes: want = input('Config file `{}` exists, do you want to override it? ' '(cannot be undone) [y/n]: '.format(config_path)) - if not want: + if want != 'y': return # Patch the default configuration with the new values diff --git a/tests/test_commands.py b/tests/test_commands.py index faa455d0..57ebdbe9 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -144,3 +144,22 @@ def test_run_configure_when_config_does_not_exist(monkeypatch, args = Namespace(config='foo', yes=True) return_value = run_configure(args) assert return_value is None + + +def test_run_configure_when_config_does_exist(monkeypatch, + mock_write_config, + mock_generate_key_pair, + mock_bigchaindb_backup_config): + value = {} + def mock_write_config(newconfig, filename=None): + value['return'] = newconfig + + from bigchaindb.commands.bigchain import run_configure + monkeypatch.setattr('os.path.exists', lambda path: True) + monkeypatch.setattr('builtins.input', lambda question: '\n') + monkeypatch.setattr('bigchaindb.config_utils.write_config', mock_write_config) + + args = Namespace(config='foo', yes=None) + run_configure(args) + assert value == {} + diff --git a/tests/utils/test_config_utils.py b/tests/utils/test_config_utils.py index b95db9aa..57539246 100644 --- a/tests/utils/test_config_utils.py +++ b/tests/utils/test_config_utils.py @@ -5,7 +5,7 @@ import pytest import bigchaindb -ORIGINAL_CONFIG = copy.deepcopy(bigchaindb.config) +ORIGINAL_CONFIG = copy.deepcopy(bigchaindb._config) @pytest.fixture(scope='function', autouse=True) From cfbce45fa3aa71ba8d6ca6a50540a29e902e33c2 Mon Sep 17 00:00:00 2001 From: Troy McConaghy Date: Tue, 1 Mar 2016 11:31:39 +0100 Subject: [PATCH 38/44] In PyStyleGuide: hard-max line-length is 119 chars Added the following sentence to the Python Style Guide section on Maximum Line Length: "As a hard limit, keep all lines less than 119 characters (which is the width of GitHub code review)." --- PYTHON_STYLE_GUIDE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PYTHON_STYLE_GUIDE.md b/PYTHON_STYLE_GUIDE.md index d416ad65..dc7e68d7 100644 --- a/PYTHON_STYLE_GUIDE.md +++ b/PYTHON_STYLE_GUIDE.md @@ -20,7 +20,7 @@ PEP8 says some things about docstrings, but not what to put in them or how to st PEP8 has some [maximum line length guidelines](https://www.python.org/dev/peps/pep-0008/#id17), starting with "Limit all lines to a maximum of 79 characters" but "for flowing long blocks of text with fewer structural restrictions (docstrings or comments), the line length should be limited to 72 characters." -We discussed this at length, and it seems that the consensus is: try to keep line lengths less than 79/72 characters, unless you have a special situation where longer lines would improve readability. (The basic reason is that 79/72 works for everyone, and BigchainDB is an open source project.) +We discussed this at length, and it seems that the consensus is: _try_ to keep line lengths less than 79/72 characters, unless you have a special situation where longer lines would improve readability. (The basic reason is that 79/72 works for everyone, and BigchainDB is an open source project.) As a hard limit, keep all lines less than 119 characters (which is the width of GitHub code review). ### Single or Double Quotes? From b051fc16390666030d4390e078247753ed9ab267 Mon Sep 17 00:00:00 2001 From: troymc Date: Tue, 1 Mar 2016 11:45:04 +0100 Subject: [PATCH 39/44] Docs: note OS-level dependencies on Fedora --- docs/source/installing.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/source/installing.md b/docs/source/installing.md index 9ff24298..e543aa06 100644 --- a/docs/source/installing.md +++ b/docs/source/installing.md @@ -27,12 +27,18 @@ If you don't already have it, then you should [install Python 3.4+](https://www. BigchainDB has some OS-level dependencies. In particular, you need to install the OS-level dependencies for the Python **cryptography** package. Instructions for installing those dependencies on your OS can be found in the [cryptography package documentation](https://cryptography.io/en/latest/installation/). -On Ubuntu 14.04, we found that the following was enough (YMMV): +On Ubuntu 14.04, we found that the following was enough: ```text $ sudo apt-get update $ sudo apt-get install libffi-dev g++ libssl-dev python3-dev ``` +On Fedora 23, we found that the following was enough (tested in February 2015): +```text +$ yum update +$ yum install libffi-devel gcc-c++ redhat-rpm-config python3-devel openssl-devel +``` + With OS-level dependencies installed, you can install BigchainDB with `pip` or from source. ### How to Install BigchainDB with `pip` From f7bfbdf457f2fe9bd4306a24e3a56b99d7a6ea16 Mon Sep 17 00:00:00 2001 From: troymc Date: Tue, 1 Mar 2016 12:20:29 +0100 Subject: [PATCH 40/44] Change yum to dnf in Fedora install notes --- docs/source/installing.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/source/installing.md b/docs/source/installing.md index e543aa06..3e38013f 100644 --- a/docs/source/installing.md +++ b/docs/source/installing.md @@ -35,10 +35,12 @@ $ sudo apt-get install libffi-dev g++ libssl-dev python3-dev On Fedora 23, we found that the following was enough (tested in February 2015): ```text -$ yum update -$ yum install libffi-devel gcc-c++ redhat-rpm-config python3-devel openssl-devel +$ sudo dnf update +$ sudo dnf install libffi-devel gcc-c++ redhat-rpm-config python3-devel openssl-devel ``` +(If you're using a version of Fedora before version 22, you may have to use `yum` instead of `dnf`.) + With OS-level dependencies installed, you can install BigchainDB with `pip` or from source. ### How to Install BigchainDB with `pip` From dd694f18febbf231f0cd11acadf3e3eabd1b86ff Mon Sep 17 00:00:00 2001 From: vrde Date: Wed, 2 Mar 2016 01:12:42 +0100 Subject: [PATCH 41/44] Use the default ReadTheDocs theme --- docs/source/conf.py | 8 ++++++-- setup.py | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 3651eb79..04f749df 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -28,6 +28,9 @@ from recommonmark.parser import CommonMarkParser # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. +import sphinx_rtd_theme + + extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.intersphinx', @@ -119,7 +122,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 = 'alabaster' +html_theme = 'sphinx_rtd_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 @@ -127,7 +131,7 @@ html_theme = 'alabaster' #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] +html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". diff --git a/setup.py b/setup.py index 3fd8dae4..caef7b69 100644 --- a/setup.py +++ b/setup.py @@ -26,6 +26,7 @@ docs_require = [ 'recommonmark>=0.4.0', 'Sphinx>=1.3.5', 'sphinxcontrib-napoleon>=0.4.4', + 'sphinx-rtd-theme>=0.1.9', ] setup( From a5ee9c1f5591f68b80f7e506f85aab9babd692db Mon Sep 17 00:00:00 2001 From: vrde Date: Wed, 2 Mar 2016 12:28:54 +0100 Subject: [PATCH 42/44] Remove useless requirements.txt --- docs/requirements.txt | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 docs/requirements.txt diff --git a/docs/requirements.txt b/docs/requirements.txt deleted file mode 100644 index 3ffbffed..00000000 --- a/docs/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -Sphinx==1.3.5 -sphinxcontrib-napoleon==0.4.4 -recommonmark From 909e0111402600eb61f1192bc5aadfcaa9f0f56d Mon Sep 17 00:00:00 2001 From: vrde Date: Wed, 2 Mar 2016 14:17:23 +0100 Subject: [PATCH 43/44] Revert "Remove useless requirements.txt" This reverts commit a5ee9c1f5591f68b80f7e506f85aab9babd692db. --- docs/requirements.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 docs/requirements.txt diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 00000000..3ffbffed --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,3 @@ +Sphinx==1.3.5 +sphinxcontrib-napoleon==0.4.4 +recommonmark From 1fd3e5e59ef212ec1f3a248d79c7ad065bc779bb Mon Sep 17 00:00:00 2001 From: vrde Date: Wed, 2 Mar 2016 14:18:11 +0100 Subject: [PATCH 44/44] Add new dep to docs/requirements.txt --- docs/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/requirements.txt b/docs/requirements.txt index 3ffbffed..9e6b6214 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,3 +1,4 @@ Sphinx==1.3.5 sphinxcontrib-napoleon==0.4.4 +sphinx-rtd-theme>=0.1.9 recommonmark