From 633aa887b260f9267fb5983773a1e0876e1ef0b7 Mon Sep 17 00:00:00 2001 From: Nevavuori Petteri Date: Thu, 17 May 2018 15:59:24 +0300 Subject: [PATCH] Cryptocurrency implemented. --- ...ing a General Purpose Cryptocurrency.ipynb | 823 ++++++++++++++++-- 1 file changed, 771 insertions(+), 52 deletions(-) diff --git a/notebooks/II. Building a General Purpose Cryptocurrency.ipynb b/notebooks/II. Building a General Purpose Cryptocurrency.ipynb index 41879d5..ce6f5a5 100644 --- a/notebooks/II. Building a General Purpose Cryptocurrency.ipynb +++ b/notebooks/II. Building a General Purpose Cryptocurrency.ipynb @@ -20,7 +20,7 @@ }, "source": [ "

Table of Contents

\n", - "
" + "
" ] }, { @@ -84,7 +84,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#### 1.2.1 Calculating the Hash\n", + "#### Calculating the Hash\n", "\n", "The nonce isn't however an infinite number. It is an 32-bit unsigned integer, which essentially means that there are \n", "\n", @@ -166,7 +166,18 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#### Transaction fees\n", + "#### Rewards\n", + "\n", + "Whil not covered in the lectures, an important question to ask is that where are the mining rewards issued from. A good discussion on the topic with the question title being [Does the block reward comes from a bitcoin treasury? If yes, then how is it issued?](https://bitcoin.stackexchange.com/questions/57683/does-the-block-reward-comes-from-a-bitcoin-treasury-if-yes-then-how-is-it-issu) provides an adequate answer:\n", + "\n", + ">There is no treasury. The Bitcoin is generated out of nothing. The coinbase transaction has special rules. It is allowed to have only one input which has no previous output and really no value. It is allowed to create outputs which have a total value of the block subsidy (currently 12.5 BTC) plus the transaction fees from that block. Those coins aren't issued by any entity; the miner just creates the output(s) and is allowed to do so. This rule is enforced by all of the full nodes on the network who will reject that miner's block if he pays himself too much money. Simply put, the miner is allowed to just pay himself money that is produced out of nothing. *(Andrew Chow at Aug 6'17)*" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Transaction Fees\n", "\n", "If the transactions were the only things in the chains, the miners doing the work of solidifying the ledgers with transcations would be forced to work for free. This is where the notion of transaction fees comes in to play. While a fee isn't explicitly defined within a transaction, the fee is added as an extra amount to the input amount. This extra is reserved for the miners to be allocated as fees for them for their efforts. Thus, \n", "\n", @@ -203,9 +214,7 @@ { "cell_type": "code", "execution_count": 1, - "metadata": { - "scrolled": false - }, + "metadata": {}, "outputs": [ { "name": "stderr", @@ -225,7 +234,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "127.0.0.1 - - [16/May/2018 09:30:39] \"POST /blocks HTTP/1.1\" 200 -\n" + "127.0.0.1 - - [17/May/2018 15:57:04] \"POST /blocks HTTP/1.1\" 200 -\n" ] }, { @@ -235,9 +244,9 @@ "{\n", " \"block\": {\n", " \"index\": 1,\n", - " \"previous_hash\": \"8398175617abaab684804c4efb8769504c1477f42f66d93198607494b9743d99\",\n", + " \"previous_hash\": \"6363c711f4dbdab3a32293abac7b34f5fd01d4366fcf4b715fb76e307aa79c43\",\n", " \"proof\": 533,\n", - " \"timestamp\": \"2018-05-16 09:30:39.236224\"\n", + " \"timestamp\": \"2018-05-17 15:57:04.830259\"\n", " },\n", " \"message\": \"Congratulations, you just mined a Block!\"\n", "}\n" @@ -247,7 +256,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "127.0.0.1 - - [16/May/2018 09:30:40] \"GET /blocks HTTP/1.1\" 200 -\n" + "127.0.0.1 - - [17/May/2018 15:57:05] \"GET /blocks HTTP/1.1\" 200 -\n" ] }, { @@ -255,18 +264,18 @@ "output_type": "stream", "text": [ "{\n", - " \"blockchain\": [\n", + " \"chain\": [\n", " {\n", " \"index\": 0,\n", " \"previous_hash\": \"0\",\n", " \"proof\": 1,\n", - " \"timestamp\": \"2018-05-16 09:30:38.222588\"\n", + " \"timestamp\": \"2018-05-17 15:57:03.817130\"\n", " },\n", " {\n", " \"index\": 1,\n", - " \"previous_hash\": \"8398175617abaab684804c4efb8769504c1477f42f66d93198607494b9743d99\",\n", + " \"previous_hash\": \"6363c711f4dbdab3a32293abac7b34f5fd01d4366fcf4b715fb76e307aa79c43\",\n", " \"proof\": 533,\n", - " \"timestamp\": \"2018-05-16 09:30:39.236224\"\n", + " \"timestamp\": \"2018-05-17 15:57:04.830259\"\n", " }\n", " ],\n", " \"length\": 2\n", @@ -277,7 +286,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "127.0.0.1 - - [16/May/2018 09:30:41] \"GET /blocks/validate HTTP/1.1\" 200 -\n" + "127.0.0.1 - - [17/May/2018 15:57:06] \"GET /blocks/validate HTTP/1.1\" 200 -\n" ] }, { @@ -294,11 +303,12 @@ "name": "stderr", "output_type": "stream", "text": [ - "127.0.0.1 - - [16/May/2018 09:30:42] \"GET /shutdown HTTP/1.1\" 200 -\n" + "127.0.0.1 - - [17/May/2018 15:57:07] \"GET /shutdown HTTP/1.1\" 200 -\n" ] } ], "source": [ + "import requests\n", "import json\n", "from blockchain.apps import BlockchainApp\n", "\n", @@ -306,13 +316,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))" ] }, @@ -327,20 +337,15 @@ }, { "cell_type": "code", - "execution_count": 3, - "metadata": { - "collapsed": true - }, + "execution_count": 2, + "metadata": {}, "outputs": [], "source": [ "import datetime\n", - "import hashlib\n", - "import json\n", - "import requests\n", + "from urllib.parse import urlparse\n", "\n", - "from flask import Flask, jsonify, request\n", + "import requests\n", "from uuid import uuid4\n", - "from urllib.parse import urlparse\n", "\n", "from blockchain.chains import Blockchain\n", "\n", @@ -349,10 +354,10 @@ "\n", " def __init__(self):\n", "\n", - " self.chain = []\n", + " self.node_address = str(uuid4()).replace('-', '')\n", " self.transactions = []\n", - " self.create_block(proof=1, previous_hash='0')\n", - " self.network = set()\n", + " self.nodes = set()\n", + " super().__init__()\n", "\n", " def create_block(self, proof, previous_hash):\n", " \"Create a block with new transactions.\"\n", @@ -372,9 +377,10 @@ " def add_transaction(self, sender, receiver, amount):\n", " \"Add a transaction to the list of transactions.\"\n", "\n", - " self.transactions.append({'sender': sender,\n", - " 'receiver': receiver,\n", - " 'amount': amount})\n", + " self.transactions.append(\n", + " {'sender': sender,\n", + " 'receiver': receiver,\n", + " 'amount': amount})\n", "\n", " return self.get_previous_block()['index'] + 1\n", "\n", @@ -382,37 +388,37 @@ " \"Add a node to the GeneralCoin network.\"\n", "\n", " parsed_url = urlparse(address)\n", - " self.network.add(parsed_url.netloc)\n", + " self.nodes.add(parsed_url.netloc)\n", "\n", " def replace_chain(self):\n", " \"Scan the network for longest chain and replace the current accordingly.\"\n", - "\n", + " \n", " longest_chain = None\n", " longest_chain_length = len(self.chain)\n", "\n", - " for node in self.network:\n", - "\n", + " for node in self.nodes:\n", + " \n", " response = requests.get(f'http://{node}/blocks')\n", " \n", " if not response.status_code == 200:\n", - " \n", + "\n", " print(f'Bad response from {node}: {response.status_code}')\n", " continue\n", - " \n", + "\n", " node_chain = response.json()['chain']\n", " node_chain_length = response.json()['length']\n", - " \n", - " if node_chain_length > longest_chain_length and self.is_chain_valid(chain):\n", - " \n", + "\n", + " if node_chain_length > longest_chain_length and self.is_chain_valid(node_chain):\n", + "\n", " longest_chain_length = node_chain_length\n", " longest_chain = node_chain\n", - " \n", + " \n", " if longest_chain is not None:\n", - " \n", + "\n", " self.chain = longest_chain\n", - " \n", + "\n", " return True\n", - " \n", + "\n", " return False" ] }, @@ -422,17 +428,730 @@ "source": [ "## 3. Creating the Web App\n", "\n", - "Then, as was the case with the general purpose Blockchain, we will continue by creating a Flask based web app that is ran locally in a thread to ensure that the app is runnable from the notebook." + "Then, as was the case with the general purpose Blockchain, we will continue by creating a Flask based web app that is ran locally in a thread to ensure that the app is runnable from the notebook. We will be moving a similar path to the implementation of the GeneralCoin, which is inheriting the ``BlockchainApp`` and expanding it." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "import requests\n", + "import json\n", + "from flask import jsonify, request\n", + "\n", + "from blockchain.apps import BlockchainApp\n", + "\n", + "\n", + "class GeneralCoinApp(BlockchainApp):\n", + "\n", + " def __init__(self, host='localhost', port=5000, chain=GeneralCoin):\n", + " super().__init__(host, port, chain)\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='/transactions',\n", + " view_func=self.api_transactions,\n", + " methods=['POST']\n", + " )\n", + " self.app.add_url_rule(\n", + " rule='/nodes',\n", + " view_func=self.api_nodes,\n", + " methods=['POST']\n", + " )\n", + " self.app.add_url_rule(\n", + " rule='/nodes/chains',\n", + " view_func=self.api_chains\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 \n", + " to the chain with a mining reward from the GeneralCoin coinbase.\"\"\"\n", + "\n", + " if request.method == 'GET':\n", + "\n", + " response = {'chain': self.chain.chain,\n", + " 'length': len(self.chain.chain)}\n", + "\n", + " return (json.dumps(response), 200)\n", + " \n", + " if request.method == 'POST':\n", + "\n", + " prev_block = self.chain.get_previous_block()\n", + " prev_hash = self.chain.hash_block(prev_block)\n", + " prev_proof = prev_block['proof']\n", + " proof = self.chain.proof_of_work(prev_proof)\n", + "\n", + " self.chain.add_transaction(\n", + " sender='GC Coinbase',\n", + " receiver=self.chain.node_address,\n", + " amount=10)\n", + "\n", + " block = self.chain.create_block(proof, prev_hash)\n", + "\n", + " response = {'message': 'Congratulations, you just mined a Block!',\n", + " 'block': block}\n", + "\n", + " return (jsonify(response), 200)\n", + "\n", + " def api_transactions(self):\n", + " \"Post a new transaction to the node provided as a JSON-dictionary.\"\n", + "\n", + " request_json = request.get_json()\n", + "\n", + " if not all(key in request_json for key in ['sender', 'receiver', 'amount']):\n", + "\n", + " return jsonify({'message': f'Bad transaction data: {request_json}'}), 400\n", + "\n", + " next_block = self.chain.add_transaction(\n", + " sender=request_json['sender'],\n", + " receiver=request_json['receiver'],\n", + " amount=request_json['amount'])\n", + "\n", + " return jsonify({'message': f'The transaction will be added to Block {next_block}',\n", + " 'transaction': request_json}), 200\n", + "\n", + " def api_nodes(self):\n", + " \"Connect nodes in a list of nodes to the network\"\n", + "\n", + " request_json = request.get_json()\n", + "\n", + " if not 'nodes' in request_json or len(request_json['nodes']) == 0:\n", + "\n", + " return jsonify({'message': 'No node data'}), 400\n", + "\n", + " for node in request_json['nodes']:\n", + "\n", + " self.chain.add_node(node)\n", + "\n", + " return jsonify({'message': f'Nodes connected', 'nodes': list(self.chain.nodes)}), 200\n", + "\n", + " def api_chains(self):\n", + " \"Query the nodes for their chains and possibly replace node's chain with the longest one\"\n", + " \n", + " replaced = self.chain.replace_chain()\n", + " \n", + " if replaced:\n", + "\n", + " response = {'message': 'Chain of the Node was replaced'}\n", + "\n", + " else:\n", + "\n", + " response = {'message': 'Chain of the Node was the longest'}\n", + "\n", + " return (jsonify(response), 200)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Running the Multi-Node GeneralCoin Network\n", + "\n", + "The last step is to initialize a distributed network of nodes for the GeneralCoin. We will initialize three nodes to a local IP with distinct ports in the range ``5000``-``5002``. We will run the nodes in their own cells to see the outputs. The outputs are generated as subsequent cells are ran, so at the initial state there are no printouts other than the ``* Running on ...`` initialization message." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "import requests\n", + "\n", + "node_0 = GeneralCoinApp(port=5000)\n", + "node_1 = GeneralCoinApp(port=5001)\n", + "node_2 = GeneralCoinApp(port=5002)\n", + "\n", + "nodes = [node_0,node_1,node_2]" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + " * Running on http://localhost:5000/ (Press CTRL+C to quit)\n" + ] + } + ], + "source": [ + "node_0.start()" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + " * Running on http://localhost:5001/ (Press CTRL+C to quit)\n" + ] + } + ], + "source": [ + "node_1.start()" + ] + }, + { + "cell_type": "code", + "execution_count": 7, "metadata": { - "collapsed": true + "scrolled": true }, - "outputs": [], - "source": [] + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + " * Running on http://localhost:5002/ (Press CTRL+C to quit)\n" + ] + } + ], + "source": [ + "node_2.start()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 4.1 Connecting the Nodes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Every node is an isolated network at the time of initialization. Let's thus start by connecting the nodes." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "http://localhost:5000/nodes\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "127.0.0.1 - - [17/May/2018 15:57:09] \"POST /nodes HTTP/1.1\" 200 -\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\n", + " \"message\": \"Nodes connected\",\n", + " \"nodes\": [\n", + " \"localhost:5001\",\n", + " \"localhost:5002\"\n", + " ]\n", + "}\n", + "http://localhost:5001/nodes\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "127.0.0.1 - - [17/May/2018 15:57:10] \"POST /nodes HTTP/1.1\" 200 -\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\n", + " \"message\": \"Nodes connected\",\n", + " \"nodes\": [\n", + " \"localhost:5002\",\n", + " \"localhost:5000\"\n", + " ]\n", + "}\n", + "http://localhost:5002/nodes\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "127.0.0.1 - - [17/May/2018 15:57:11] \"POST /nodes HTTP/1.1\" 200 -\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\n", + " \"message\": \"Nodes connected\",\n", + " \"nodes\": [\n", + " \"localhost:5001\",\n", + " \"localhost:5000\"\n", + " ]\n", + "}\n" + ] + } + ], + "source": [ + "for i in range(3):\n", + " \n", + " data = {'nodes':[node.host_url for node in nodes if node != nodes[i]]}\n", + " print(f'{nodes[i].host_url}/nodes')\n", + " response = requests.post(f'{nodes[i].host_url}/nodes', json=data)\n", + " print(json.dumps(response.json(), indent=2))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 4.2 Transactions and Mining with Single Node\n", + "\n", + "Then we'll test out issuing some transactions and mining blocks on a single node." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "127.0.0.1 - - [17/May/2018 15:57:12] \"POST /transactions HTTP/1.1\" 200 -\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\n", + " \"message\": \"The transaction will be added to Block 1\",\n", + " \"transaction\": {\n", + " \"amount\": 2.0,\n", + " \"receiver\": \"user_b\",\n", + " \"sender\": \"user_a\"\n", + " }\n", + "}\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "127.0.0.1 - - [17/May/2018 15:57:13] \"POST /transactions HTTP/1.1\" 200 -\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\n", + " \"message\": \"The transaction will be added to Block 1\",\n", + " \"transaction\": {\n", + " \"amount\": 3.5,\n", + " \"receiver\": \"user_c\",\n", + " \"sender\": \"user_a\"\n", + " }\n", + "}\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "127.0.0.1 - - [17/May/2018 15:57:14] \"POST /transactions HTTP/1.1\" 200 -\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\n", + " \"message\": \"The transaction will be added to Block 1\",\n", + " \"transaction\": {\n", + " \"amount\": 0.5,\n", + " \"receiver\": \"user_c\",\n", + " \"sender\": \"user_b\"\n", + " }\n", + "}\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "127.0.0.1 - - [17/May/2018 15:57:15] \"POST /blocks HTTP/1.1\" 200 -\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\n", + " \"block\": {\n", + " \"index\": 1,\n", + " \"previous_hash\": \"6fafee8d0b47ed087953fd6aecfdd2e0e02edffa9fabcb0a72dcfd46d3bdad69\",\n", + " \"proof\": 533,\n", + " \"timestamp\": \"2018-05-17 15:57:15.169925\",\n", + " \"transactions\": [\n", + " {\n", + " \"amount\": 2.0,\n", + " \"receiver\": \"user_b\",\n", + " \"sender\": \"user_a\"\n", + " },\n", + " {\n", + " \"amount\": 3.5,\n", + " \"receiver\": \"user_c\",\n", + " \"sender\": \"user_a\"\n", + " },\n", + " {\n", + " \"amount\": 0.5,\n", + " \"receiver\": \"user_c\",\n", + " \"sender\": \"user_b\"\n", + " },\n", + " {\n", + " \"amount\": 10,\n", + " \"receiver\": \"e5130752d8c74b148c63d4ddd4c941d9\",\n", + " \"sender\": \"GC Coinbase\"\n", + " }\n", + " ]\n", + " },\n", + " \"message\": \"Congratulations, you just mined a Block!\"\n", + "}\n" + ] + } + ], + "source": [ + "transactions = [\n", + " {'sender': 'user_a', 'receiver': 'user_b', 'amount': 2.0},\n", + " {'sender': 'user_a', 'receiver': 'user_c', 'amount': 3.5},\n", + " {'sender': 'user_b', 'receiver': 'user_c', 'amount': 0.5},\n", + "]\n", + "\n", + "for transaction in transactions:\n", + "\n", + " response = requests.post( f'{node_0.host_url}/transactions', \n", + " json=transaction)\n", + " print(json.dumps(response.json(), indent=2))\n", + " \n", + "response = requests.post(f'{node_0.host_url}/blocks')\n", + "print(json.dumps(response.json(), indent=2))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 4.3 Broadcasting the Chain to the Network\n", + "\n", + "We then broadcast the chain to other nodes." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Updating chain for node http://localhost:5000\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "127.0.0.1 - - [17/May/2018 15:57:17] \"GET /blocks HTTP/1.1\" 200 -\n", + "127.0.0.1 - - [17/May/2018 15:57:18] \"GET /blocks HTTP/1.1\" 200 -\n", + "127.0.0.1 - - [17/May/2018 15:57:18] \"GET /nodes/chains HTTP/1.1\" 200 -\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\n", + " \"message\": \"Chain of the Node was the longest\"\n", + "}\n", + "Updating chain for node http://localhost:5001\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "127.0.0.1 - - [17/May/2018 15:57:20] \"GET /blocks HTTP/1.1\" 200 -\n", + "127.0.0.1 - - [17/May/2018 15:57:21] \"GET /blocks HTTP/1.1\" 200 -\n", + "127.0.0.1 - - [17/May/2018 15:57:21] \"GET /nodes/chains HTTP/1.1\" 200 -\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\n", + " \"message\": \"Chain of the Node was replaced\"\n", + "}\n", + "Updating chain for node http://localhost:5002\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "127.0.0.1 - - [17/May/2018 15:57:23] \"GET /blocks HTTP/1.1\" 200 -\n", + "127.0.0.1 - - [17/May/2018 15:57:24] \"GET /blocks HTTP/1.1\" 200 -\n", + "127.0.0.1 - - [17/May/2018 15:57:24] \"GET /nodes/chains HTTP/1.1\" 200 -\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\n", + " \"message\": \"Chain of the Node was replaced\"\n", + "}\n" + ] + } + ], + "source": [ + "for i in range(3):\n", + " \n", + " print(f'Updating chain for node {nodes[i].host_url}')\n", + " response = requests.get(f'{nodes[i].host_url}/nodes/chains')\n", + " print(json.dumps(response.json(), indent=2))\n", + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's print out every node's current chain." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "http://localhost:5000\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "127.0.0.1 - - [17/May/2018 15:58:02] \"GET /blocks HTTP/1.1\" 200 -\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\n", + " \"chain\": [\n", + " {\n", + " \"index\": 0,\n", + " \"timestamp\": \"2018-05-17 15:57:07.966990\",\n", + " \"proof\": 1,\n", + " \"previous_hash\": \"0\",\n", + " \"transactions\": []\n", + " },\n", + " {\n", + " \"index\": 1,\n", + " \"timestamp\": \"2018-05-17 15:57:15.169925\",\n", + " \"proof\": 533,\n", + " \"previous_hash\": \"6fafee8d0b47ed087953fd6aecfdd2e0e02edffa9fabcb0a72dcfd46d3bdad69\",\n", + " \"transactions\": [\n", + " {\n", + " \"sender\": \"user_a\",\n", + " \"receiver\": \"user_b\",\n", + " \"amount\": 2.0\n", + " },\n", + " {\n", + " \"sender\": \"user_a\",\n", + " \"receiver\": \"user_c\",\n", + " \"amount\": 3.5\n", + " },\n", + " {\n", + " \"sender\": \"user_b\",\n", + " \"receiver\": \"user_c\",\n", + " \"amount\": 0.5\n", + " },\n", + " {\n", + " \"sender\": \"GC Coinbase\",\n", + " \"receiver\": \"e5130752d8c74b148c63d4ddd4c941d9\",\n", + " \"amount\": 10\n", + " }\n", + " ]\n", + " }\n", + " ],\n", + " \"length\": 2\n", + "}\n", + "http://localhost:5001\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "127.0.0.1 - - [17/May/2018 15:58:03] \"GET /blocks HTTP/1.1\" 200 -\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\n", + " \"chain\": [\n", + " {\n", + " \"index\": 0,\n", + " \"timestamp\": \"2018-05-17 15:57:07.966990\",\n", + " \"proof\": 1,\n", + " \"previous_hash\": \"0\",\n", + " \"transactions\": []\n", + " },\n", + " {\n", + " \"index\": 1,\n", + " \"timestamp\": \"2018-05-17 15:57:15.169925\",\n", + " \"proof\": 533,\n", + " \"previous_hash\": \"6fafee8d0b47ed087953fd6aecfdd2e0e02edffa9fabcb0a72dcfd46d3bdad69\",\n", + " \"transactions\": [\n", + " {\n", + " \"sender\": \"user_a\",\n", + " \"receiver\": \"user_b\",\n", + " \"amount\": 2.0\n", + " },\n", + " {\n", + " \"sender\": \"user_a\",\n", + " \"receiver\": \"user_c\",\n", + " \"amount\": 3.5\n", + " },\n", + " {\n", + " \"sender\": \"user_b\",\n", + " \"receiver\": \"user_c\",\n", + " \"amount\": 0.5\n", + " },\n", + " {\n", + " \"sender\": \"GC Coinbase\",\n", + " \"receiver\": \"e5130752d8c74b148c63d4ddd4c941d9\",\n", + " \"amount\": 10\n", + " }\n", + " ]\n", + " }\n", + " ],\n", + " \"length\": 2\n", + "}\n", + "http://localhost:5002\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "127.0.0.1 - - [17/May/2018 15:58:04] \"GET /blocks HTTP/1.1\" 200 -\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\n", + " \"chain\": [\n", + " {\n", + " \"index\": 0,\n", + " \"timestamp\": \"2018-05-17 15:57:07.966990\",\n", + " \"proof\": 1,\n", + " \"previous_hash\": \"0\",\n", + " \"transactions\": []\n", + " },\n", + " {\n", + " \"index\": 1,\n", + " \"timestamp\": \"2018-05-17 15:57:15.169925\",\n", + " \"proof\": 533,\n", + " \"previous_hash\": \"6fafee8d0b47ed087953fd6aecfdd2e0e02edffa9fabcb0a72dcfd46d3bdad69\",\n", + " \"transactions\": [\n", + " {\n", + " \"sender\": \"user_a\",\n", + " \"receiver\": \"user_b\",\n", + " \"amount\": 2.0\n", + " },\n", + " {\n", + " \"sender\": \"user_a\",\n", + " \"receiver\": \"user_c\",\n", + " \"amount\": 3.5\n", + " },\n", + " {\n", + " \"sender\": \"user_b\",\n", + " \"receiver\": \"user_c\",\n", + " \"amount\": 0.5\n", + " },\n", + " {\n", + " \"sender\": \"GC Coinbase\",\n", + " \"receiver\": \"e5130752d8c74b148c63d4ddd4c941d9\",\n", + " \"amount\": 10\n", + " }\n", + " ]\n", + " }\n", + " ],\n", + " \"length\": 2\n", + "}\n" + ] + } + ], + "source": [ + "for i in range(3):\n", + " \n", + " print(f'{nodes[i].host_url}')\n", + " response = requests.get(f'{nodes[i].host_url}/blocks')\n", + " print(json.dumps(response.json(), indent=2))\n", + " " + ] } ], "metadata": {