diff --git a/notebooks/I. Building a General Purpose Blockchain.ipynb b/notebooks/I. Building a General Purpose Blockchain.ipynb index d571f12..1b31794 100644 --- a/notebooks/I. Building a General Purpose Blockchain.ipynb +++ b/notebooks/I. Building a General Purpose Blockchain.ipynb @@ -60,7 +60,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -162,7 +162,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ @@ -175,29 +175,17 @@ "\n", "class BlockchainApp:\n", "\n", - " def __init__(self, host='localhost', port=5000):\n", + " def __init__(self, host='localhost', port=5000, chain=Blockchain):\n", "\n", " self.host = host\n", " self.port = port\n", - " self.chain = Blockchain()\n", + " self.chain = chain()\n", "\n", " self.host_url = f'http://{self.host}:{self.port}'\n", + " \n", " self.app = Flask(__name__)\n", - "\n", - " self.app.add_url_rule(\n", - " rule='/blocks',\n", - " view_func=self.api_blocks,\n", - " methods=['GET', 'POST']\n", - " )\n", - " self.app.add_url_rule(\n", - " rule='/blocks/validate',\n", - " view_func=self.api_validate,\n", - " )\n", - " self.app.add_url_rule(\n", - " rule='/shutdown',\n", - " view_func=self.api_shutdown,\n", - " )\n", - "\n", + " self.add_api_endpoints()\n", + " \n", " self.thread = threading.Thread(\n", " target=run_simple,\n", " kwargs={\n", @@ -215,8 +203,26 @@ " def __exit__(self, *args):\n", "\n", " self.stop()\n", + " \n", + " def add_api_endpoints(self):\n", + " \"Add API endpoints to the Flask WebApp.\"\n", + " \n", + " self.app.add_url_rule(\n", + " rule='/blocks',\n", + " view_func=self.api_blocks,\n", + " methods=['GET', 'POST']\n", + " )\n", + " self.app.add_url_rule(\n", + " rule='/blocks/validate',\n", + " view_func=self.api_validate,\n", + " )\n", + " self.app.add_url_rule(\n", + " rule='/shutdown',\n", + " view_func=self.api_shutdown,\n", + " )\n", "\n", " def api_blocks(self):\n", + " \"Either retrieve the node's current chain or post a new block to the chain.\"\n", "\n", " if request.method == 'POST':\n", "\n", @@ -241,6 +247,7 @@ " return (jsonify(response), 200)\n", "\n", " def api_validate(self):\n", + " \"Validate the chain\"\n", "\n", " if self.chain.is_chain_valid(self.chain.chain):\n", "\n", @@ -255,32 +262,23 @@ " return (jsonify(response), 500)\n", "\n", " def api_shutdown(self):\n", + " \"Shutdown the Flask WebApp\"\n", "\n", " request.environ.get('werkzeug.server.shutdown')()\n", "\n", " return jsonify({'message': 'Shutting down'}), 200\n", "\n", - " def get_blockchain(self):\n", - "\n", - " return requests.get(f'{self.host_url}/blocks')\n", - "\n", - " def mine_block(self):\n", - "\n", - " return requests.post(f'{self.host_url}/blocks')\n", - "\n", " def start(self):\n", + " \"Start the Flask-based Blockchain WebApp.\"\n", "\n", " self.thread.start()\n", "\n", " def stop(self):\n", + " \"Stop the Flask-based Blockchain WebApp.\"\n", "\n", " if self.thread.is_alive():\n", "\n", - " return requests.get(f'{self.host_url}/shutdown')\n", - "\n", - " def validate_blockchain(self):\n", - "\n", - " return requests.get(f'{self.host_url}/blocks/validate')" + " return requests.get(f'{self.host_url}/shutdown')" ] }, { @@ -317,7 +315,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "127.0.0.1 - - [16/May/2018 08:48:58] \"POST /blocks HTTP/1.1\" 200 -\n" + "127.0.0.1 - - [17/May/2018 11:23:01] \"POST /blocks HTTP/1.1\" 200 -\n" ] }, { @@ -327,9 +325,9 @@ "{\n", " \"block\": {\n", " \"index\": 1,\n", - " \"previous_hash\": \"314104ce935d2d1b559fc5c085a4fe752408f1291049737e2b867c5f5c0c0ab8\",\n", + " \"previous_hash\": \"aa037f09d677799a99bde71a5c39672432ec45b571c61bf19e300293d696d247\",\n", " \"proof\": 533,\n", - " \"timestamp\": \"2018-05-16 08:48:58.771522\"\n", + " \"timestamp\": \"2018-05-17 11:23:01.276072\"\n", " },\n", " \"message\": \"Congratulations, you just mined a Block!\"\n", "}\n" @@ -339,7 +337,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "127.0.0.1 - - [16/May/2018 08:48:59] \"GET /blocks HTTP/1.1\" 200 -\n" + "127.0.0.1 - - [17/May/2018 11:23:02] \"GET /blocks HTTP/1.1\" 200 -\n" ] }, { @@ -352,13 +350,13 @@ " \"index\": 0,\n", " \"previous_hash\": \"0\",\n", " \"proof\": 1,\n", - " \"timestamp\": \"2018-05-16 08:48:57.756766\"\n", + " \"timestamp\": \"2018-05-17 11:23:00.257932\"\n", " },\n", " {\n", " \"index\": 1,\n", - " \"previous_hash\": \"314104ce935d2d1b559fc5c085a4fe752408f1291049737e2b867c5f5c0c0ab8\",\n", + " \"previous_hash\": \"aa037f09d677799a99bde71a5c39672432ec45b571c61bf19e300293d696d247\",\n", " \"proof\": 533,\n", - " \"timestamp\": \"2018-05-16 08:48:58.771522\"\n", + " \"timestamp\": \"2018-05-17 11:23:01.276072\"\n", " }\n", " ],\n", " \"length\": 2\n", @@ -369,7 +367,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "127.0.0.1 - - [16/May/2018 08:49:00] \"GET /blocks/validate HTTP/1.1\" 200 -\n" + "127.0.0.1 - - [17/May/2018 11:23:03] \"GET /blocks/validate HTTP/1.1\" 200 -\n" ] }, { @@ -386,7 +384,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "127.0.0.1 - - [16/May/2018 08:49:01] \"GET /shutdown HTTP/1.1\" 200 -\n" + "127.0.0.1 - - [17/May/2018 11:23:04] \"GET /shutdown HTTP/1.1\" 200 -\n" ] } ], @@ -395,13 +393,13 @@ "\n", " print(blockchain_app.__class__.__name__)\n", "\n", - " response = blockchain_app.mine_block()\n", + " response = requests.post(f'{blockchain_app.host_url}/blocks')\n", " print(json.dumps(response.json(), indent=2))\n", "\n", - " response = blockchain_app.get_blockchain()\n", + " response = requests.get(f'{blockchain_app.host_url}/blocks')\n", " print(json.dumps(response.json(), indent=2))\n", " \n", - " response = blockchain_app.validate_blockchain()\n", + " response = requests.get(f'{blockchain_app.host_url}/blocks/validate')\n", " print(json.dumps(response.json(), indent=2))" ] }, @@ -424,7 +422,7 @@ "output_type": "stream", "text": [ " * Running on http://localhost:5000/ (Press CTRL+C to quit)\n", - "127.0.0.1 - - [16/May/2018 08:49:06] \"POST /blocks HTTP/1.1\" 200 -\n" + "127.0.0.1 - - [17/May/2018 11:23:35] \"POST /blocks HTTP/1.1\" 200 -\n" ] }, { @@ -438,7 +436,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "127.0.0.1 - - [16/May/2018 08:49:07] \"POST /blocks HTTP/1.1\" 200 -\n" + "127.0.0.1 - - [17/May/2018 11:23:36] \"POST /blocks HTTP/1.1\" 200 -\n" ] }, { @@ -452,7 +450,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "127.0.0.1 - - [16/May/2018 08:49:08] \"POST /blocks HTTP/1.1\" 200 -\n" + "127.0.0.1 - - [17/May/2018 11:23:37] \"POST /blocks HTTP/1.1\" 200 -\n" ] }, { @@ -466,7 +464,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "127.0.0.1 - - [16/May/2018 08:49:09] \"POST /blocks HTTP/1.1\" 200 -\n" + "127.0.0.1 - - [17/May/2018 11:23:38] \"POST /blocks HTTP/1.1\" 200 -\n" ] }, { @@ -480,7 +478,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "127.0.0.1 - - [16/May/2018 08:49:10] \"GET /blocks HTTP/1.1\" 200 -\n" + "127.0.0.1 - - [17/May/2018 11:23:39] \"GET /blocks HTTP/1.1\" 200 -\n" ] }, { @@ -493,31 +491,31 @@ " \"index\": 0,\n", " \"previous_hash\": \"0\",\n", " \"proof\": 1,\n", - " \"timestamp\": \"2018-05-16 08:49:05.457244\"\n", + " \"timestamp\": \"2018-05-17 11:23:34.489424\"\n", " },\n", " {\n", " \"index\": 1,\n", - " \"previous_hash\": \"c4123e3b57d8b70b92319436c4dad9531c7074d506d79180e2abbd206f7a433d\",\n", + " \"previous_hash\": \"0898ebe729b4094d29e88a37666eea804283b358d7ef8f71b5f4e3569f71c23c\",\n", " \"proof\": 533,\n", - " \"timestamp\": \"2018-05-16 08:49:06.467524\"\n", + " \"timestamp\": \"2018-05-17 11:23:35.499157\"\n", " },\n", " {\n", " \"index\": 2,\n", - " \"previous_hash\": \"aeee0a31814366029dd384dc8cd8a88d066aea1ec8bf378596d5fdf34c3c188a\",\n", + " \"previous_hash\": \"03aa5640ade8534dc1419b29413d8908c822c090079080139415e37c3aa89903\",\n", " \"proof\": 45293,\n", - " \"timestamp\": \"2018-05-16 08:49:07.629675\"\n", + " \"timestamp\": \"2018-05-17 11:23:36.636971\"\n", " },\n", " {\n", " \"index\": 3,\n", - " \"previous_hash\": \"758fc7f33cf7cf532ace0c7cae44b6318f6e7b0b9eabf4f686fc8ac2d09cf2e9\",\n", + " \"previous_hash\": \"6683bd4452b6f2acb9719d7cddb684bf8a5cc1ba2b56f2ab356db9731e1f792c\",\n", " \"proof\": 21391,\n", - " \"timestamp\": \"2018-05-16 08:49:08.737937\"\n", + " \"timestamp\": \"2018-05-17 11:23:37.722398\"\n", " },\n", " {\n", " \"index\": 4,\n", - " \"previous_hash\": \"f66c690dffaf0f4cd6659e6ebca69267b87d70941a651185a16434689e848188\",\n", + " \"previous_hash\": \"7658422de8b132c643537bd9afd585c535aeb2f8b655264d5a0ab91971fa2747\",\n", " \"proof\": 8018,\n", - " \"timestamp\": \"2018-05-16 08:49:09.805570\"\n", + " \"timestamp\": \"2018-05-17 11:23:38.760560\"\n", " }\n", " ],\n", " \"length\": 5\n", @@ -528,7 +526,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "127.0.0.1 - - [16/May/2018 08:49:11] \"GET /blocks/validate HTTP/1.1\" 200 -\n" + "127.0.0.1 - - [17/May/2018 11:23:40] \"GET /blocks/validate HTTP/1.1\" 200 -\n" ] }, { @@ -545,7 +543,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "127.0.0.1 - - [16/May/2018 08:49:12] \"GET /shutdown HTTP/1.1\" 200 -\n" + "127.0.0.1 - - [17/May/2018 11:23:41] \"GET /shutdown HTTP/1.1\" 200 -\n" ] } ], @@ -554,13 +552,13 @@ "\n", " for _ in range(4):\n", "\n", - " response = blockchain_app.mine_block()\n", + " response = requests.post(f'{blockchain_app.host_url}/blocks')\n", " print(\"Proof={}\".format(response.json()['block']['proof']))\n", "\n", - " response = blockchain_app.get_blockchain()\n", + " response = requests.get(f'{blockchain_app.host_url}/blocks')\n", " print(\"Chain={}\".format(json.dumps(response.json(), indent=2)))\n", " \n", - " response = blockchain_app.validate_blockchain()\n", + " response = requests.get(f'{blockchain_app.host_url}/blocks/validate')\n", " print(json.dumps(response.json(), indent=2))" ] }, @@ -582,7 +580,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 6, "metadata": {}, "outputs": [ { @@ -590,7 +588,7 @@ "output_type": "stream", "text": [ " * Running on http://localhost:5000/ (Press CTRL+C to quit)\n", - "127.0.0.1 - - [16/May/2018 08:58:59] \"POST /blocks HTTP/1.1\" 200 -\n" + "127.0.0.1 - - [17/May/2018 11:24:12] \"POST /blocks HTTP/1.1\" 200 -\n" ] }, { @@ -604,7 +602,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "127.0.0.1 - - [16/May/2018 08:59:00] \"POST /blocks HTTP/1.1\" 200 -\n" + "127.0.0.1 - - [17/May/2018 11:24:13] \"POST /blocks HTTP/1.1\" 200 -\n" ] }, { @@ -618,7 +616,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "127.0.0.1 - - [16/May/2018 08:59:01] \"POST /blocks HTTP/1.1\" 200 -\n" + "127.0.0.1 - - [17/May/2018 11:24:14] \"POST /blocks HTTP/1.1\" 200 -\n" ] }, { @@ -632,7 +630,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "127.0.0.1 - - [16/May/2018 08:59:02] \"POST /blocks HTTP/1.1\" 200 -\n" + "127.0.0.1 - - [17/May/2018 11:24:15] \"POST /blocks HTTP/1.1\" 200 -\n" ] }, { @@ -647,7 +645,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "127.0.0.1 - - [16/May/2018 08:59:03] \"GET /blocks HTTP/1.1\" 200 -\n" + "127.0.0.1 - - [17/May/2018 11:24:16] \"GET /blocks HTTP/1.1\" 200 -\n" ] }, { @@ -660,31 +658,31 @@ " \"index\": 0,\n", " \"previous_hash\": \"0\",\n", " \"proof\": 1,\n", - " \"timestamp\": \"2018-05-16 08:58:58.573088\"\n", + " \"timestamp\": \"2018-05-17 11:24:11.501229\"\n", " },\n", " {\n", " \"index\": 1,\n", - " \"previous_hash\": \"064891cce8222925dbe8f8790cd4ef562112ed82bb37a4749496d09a34d5d0d4\",\n", + " \"previous_hash\": \"3709200a7104364db3f4c7f0464e2b478ceff4a6ec9ef8c494ca946891fdb796\",\n", " \"proof\": 12345,\n", - " \"timestamp\": \"2018-05-16 08:58:59.584288\"\n", + " \"timestamp\": \"2018-05-17 11:24:12.509229\"\n", " },\n", " {\n", " \"index\": 2,\n", - " \"previous_hash\": \"9a9e09a60e3de0ee776259f9aa01201b57f8eb0dd1f00460cb3622543fe65404\",\n", + " \"previous_hash\": \"aae67119ebb026be70cf1638c0b0e9aa3aa316029e90c05273e37254cc6ac9f8\",\n", " \"proof\": 45293,\n", - " \"timestamp\": \"2018-05-16 08:59:00.731544\"\n", + " \"timestamp\": \"2018-05-17 11:24:13.646387\"\n", " },\n", " {\n", " \"index\": 3,\n", - " \"previous_hash\": \"8a24cac8d63c467faec15cee9ea9aba4615e11b9d99cc707932c3a189388f469\",\n", + " \"previous_hash\": \"dcce5c492011de9724ada1bbd8a59f0679cca429d199558930efacc158207772\",\n", " \"proof\": 21391,\n", - " \"timestamp\": \"2018-05-16 08:59:01.822083\"\n", + " \"timestamp\": \"2018-05-17 11:24:14.739003\"\n", " },\n", " {\n", " \"index\": 4,\n", - " \"previous_hash\": \"f5a7a2eeae886e7be7127448e8f2b2d5a40405eddc31265e3bf01bfa2c3919d8\",\n", + " \"previous_hash\": \"4c9dd27b0001c1a16619ace602b9a08073e42ccd5482e562fb10b6ef163dfe5a\",\n", " \"proof\": 8018,\n", - " \"timestamp\": \"2018-05-16 08:59:02.874947\"\n", + " \"timestamp\": \"2018-05-17 11:24:15.789439\"\n", " }\n", " ],\n", " \"length\": 5\n", @@ -695,7 +693,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "127.0.0.1 - - [16/May/2018 08:59:04] \"GET /blocks/validate HTTP/1.1\" 500 -\n" + "127.0.0.1 - - [17/May/2018 11:24:17] \"GET /blocks/validate HTTP/1.1\" 500 -\n" ] }, { @@ -707,7 +705,7 @@ " \"valid\": false\n", "}\n", "Hashes: \n", - "\tOriginal =\t9a9e09a60e3de0ee776259f9aa01201b57f8eb0dd1f00460cb3622543fe65404\n", + "\tOriginal =\taae67119ebb026be70cf1638c0b0e9aa3aa316029e90c05273e37254cc6ac9f8\n", "\tForged =\tb3e1a78ae4b5cd947a6690f4a5ee9b3a71a13a2df00d77cbc4c904c9b2ac2deb\n" ] }, @@ -715,7 +713,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "127.0.0.1 - - [16/May/2018 08:59:05] \"GET /shutdown HTTP/1.1\" 200 -\n" + "127.0.0.1 - - [17/May/2018 11:24:18] \"GET /shutdown HTTP/1.1\" 200 -\n" ] } ], @@ -724,17 +722,17 @@ " \n", " for _ in range(4):\n", "\n", - " response = blockchain_app.mine_block()\n", + " response = requests.post(f'{blockchain_app.host_url}/blocks')\n", " print(\"Proof={}\".format(response.json()['block']['proof']))\n", "\n", " original = blockchain_app.chain.chain[1]['proof']\n", " blockchain_app.chain.chain[1]['proof'] = 12345\n", " print('Proofs: Original={}, Forged={}'.format(original,blockchain_app.chain.chain[1]['proof']))\n", " \n", - " response = blockchain_app.get_blockchain()\n", + " response = requests.get(f'{blockchain_app.host_url}/blocks')\n", " print(json.dumps(response.json(), indent=2))\n", " \n", - " response = blockchain_app.validate_blockchain()\n", + " response = requests.get(f'{blockchain_app.host_url}/blocks/validate')\n", " print(json.dumps(response.json(), indent=2))\n", " \n", " proof_1 = blockchain_app.chain.chain[1]['proof']\n", @@ -750,7 +748,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "And so we indeed have invalidated the chain!" + "And so we indeed have invalidated the chain! The avalanche effect of the hash-function is clearly visible in the last few lines of the above printout. The hashes with the original proof and the forged one differ substantially from each other. This is what makes even slight forgeries impossible without comporising the validity of the chain." ] } ], diff --git a/notebooks/blockchain/apps.py b/notebooks/blockchain/apps.py index 9b9c14f..b74b353 100644 --- a/notebooks/blockchain/apps.py +++ b/notebooks/blockchain/apps.py @@ -1,7 +1,7 @@ import threading -import requests -from flask import Flask, request, jsonify +import requests +from flask import Flask, jsonify, request from werkzeug.serving import run_simple from . import chains @@ -9,28 +9,16 @@ class BlockchainApp: - def __init__(self, host='localhost', port=5000): + def __init__(self, host='localhost', port=5000, chain=chains.Blockchain): self.host = host self.port = port - self.chain = chains.Blockchain() + self.chain = chain() self.host_url = f'http://{self.host}:{self.port}' - self.app = Flask(__name__) - self.app.add_url_rule( - rule='/blocks', - view_func=self.api_blocks, - methods=['GET', 'POST'] - ) - self.app.add_url_rule( - rule='/blocks/validate', - view_func=self.api_validate, - ) - self.app.add_url_rule( - rule='/shutdown', - view_func=self.api_shutdown, - ) + self.app = Flask(__name__) + self.add_api_endpoints() self.thread = threading.Thread( target=run_simple, @@ -50,7 +38,25 @@ def __exit__(self, *args): self.stop() + def add_api_endpoints(self): + "Add API endpoints to the Flask WebApp." + + self.app.add_url_rule( + rule='/blocks', + view_func=self.api_blocks, + methods=['GET', 'POST'] + ) + self.app.add_url_rule( + rule='/blocks/validate', + view_func=self.api_validate, + ) + self.app.add_url_rule( + rule='/shutdown', + view_func=self.api_shutdown, + ) + def api_blocks(self): + "Either retrieve the node's current chain or post a new block to the chain." if request.method == 'POST': @@ -75,6 +81,7 @@ def api_blocks(self): return (jsonify(response), 200) def api_validate(self): + "Validate the chain" if self.chain.is_chain_valid(self.chain.chain): @@ -89,29 +96,20 @@ def api_validate(self): return (jsonify(response), 500) def api_shutdown(self): + "Shutdown the Flask WebApp" request.environ.get('werkzeug.server.shutdown')() return jsonify({'message': 'Shutting down'}), 200 - def get_blockchain(self): - - return requests.get(f'{self.host_url}/blocks') - - def mine_block(self): - - return requests.post(f'{self.host_url}/blocks') - def start(self): + "Start the Flask-based Blockchain WebApp." self.thread.start() def stop(self): + "Stop the Flask-based Blockchain WebApp." if self.thread.is_alive(): return requests.get(f'{self.host_url}/shutdown') - - def validate_blockchain(self): - - return requests.get(f'{self.host_url}/blocks/validate')