From ccdc14018e342f732c0ff951e388e25f39b88762 Mon Sep 17 00:00:00 2001 From: "Eric A. Stephens" Date: Thu, 3 Dec 2020 14:04:22 -0500 Subject: [PATCH] move python code to its own directory; split api from core blockchain class. use python 3.9 --- Pipfile.lock | 109 ---------------- README.md | 16 ++- .travis.yml => python/.travis.yml | 4 +- Dockerfile => python/Dockerfile | 5 +- Pipfile => python/Pipfile | 7 +- python/Pipfile.lock | 138 +++++++++++++++++++++ python/api.py | 114 +++++++++++++++++ blockchain.py => python/blockchain.py | 116 +---------------- python/requirements.txt | 2 + {tests => python/tests}/__init__.py | 0 {tests => python/tests}/test_blockchain.py | 0 requirements.txt | 2 - 12 files changed, 270 insertions(+), 243 deletions(-) delete mode 100644 Pipfile.lock rename .travis.yml => python/.travis.yml (71%) rename Dockerfile => python/Dockerfile (68%) rename Pipfile => python/Pipfile (65%) create mode 100644 python/Pipfile.lock create mode 100644 python/api.py rename blockchain.py => python/blockchain.py (64%) create mode 100644 python/requirements.txt rename {tests => python/tests}/__init__.py (100%) rename {tests => python/tests}/test_blockchain.py (100%) delete mode 100644 requirements.txt diff --git a/Pipfile.lock b/Pipfile.lock deleted file mode 100644 index 4d21bf73..00000000 --- a/Pipfile.lock +++ /dev/null @@ -1,109 +0,0 @@ -{ - "_meta": { - "hash": { - "sha256": "45af71184b5b013f58a8601f7f87c9f0b882afe19f197ce45d6d08e46d615159" - }, - "host-environment-markers": { - "implementation_name": "cpython", - "implementation_version": "3.6.2", - "os_name": "posix", - "platform_machine": "x86_64", - "platform_python_implementation": "CPython", - "platform_release": "4.10.0-35-generic", - "platform_system": "Linux", - "platform_version": "#39~16.04.1-Ubuntu SMP Wed Sep 13 09:02:42 UTC 2017", - "python_full_version": "3.6.2", - "python_version": "3.6", - "sys_platform": "linux" - }, - "pipfile-spec": 6, - "requires": { - "python_version": "3.6" - }, - "sources": [ - { - "name": "pypi", - "url": "https://pypi.python.org/simple", - "verify_ssl": true - } - ] - }, - "default": { - "certifi": { - "hashes": [ - "sha256:54a07c09c586b0e4c619f02a5e94e36619da8e2b053e20f594348c0611803704", - "sha256:40523d2efb60523e113b44602298f0960e900388cf3bb6043f645cf57ea9e3f5" - ], - "version": "==2017.7.27.1" - }, - "chardet": { - "hashes": [ - "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691", - "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae" - ], - "version": "==3.0.4" - }, - "click": { - "hashes": [ - "sha256:29f99fc6125fbc931b758dc053b3114e55c77a6e4c6c3a2674a2dc986016381d", - "sha256:f15516df478d5a56180fbf80e68f206010e6d160fc39fa508b65e035fd75130b" - ], - "version": "==6.7" - }, - "flask": { - "hashes": [ - "sha256:0749df235e3ff61ac108f69ac178c9770caeaccad2509cb762ce1f65570a8856", - "sha256:49f44461237b69ecd901cc7ce66feea0319b9158743dd27a2899962ab214dac1" - ], - "version": "==0.12.2" - }, - "idna": { - "hashes": [ - "sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4", - "sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f" - ], - "version": "==2.6" - }, - "itsdangerous": { - "hashes": [ - "sha256:cbb3fcf8d3e33df861709ecaf89d9e6629cff0a217bc2848f1b41cd30d360519" - ], - "version": "==0.24" - }, - "jinja2": { - "hashes": [ - "sha256:2231bace0dfd8d2bf1e5d7e41239c06c9e0ded46e70cc1094a0aa64b0afeb054", - "sha256:ddaa01a212cd6d641401cb01b605f4a4d9f37bfc93043d7f760ec70fb99ff9ff" - ], - "version": "==2.9.6" - }, - "markupsafe": { - "hashes": [ - "sha256:a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665" - ], - "version": "==1.0" - }, - "requests": { - "hashes": [ - "sha256:6a1b267aa90cac58ac3a765d067950e7dbbf75b1da07e895d1f594193a40a38b", - "sha256:9c443e7324ba5b85070c4a818ade28bfabedf16ea10206da1132edaa6dda237e" - ], - "version": "==2.18.4" - }, - "urllib3": { - "hashes": [ - "sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b", - "sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f" - ], - "version": "==1.22" - }, - "werkzeug": { - "hashes": [ - "sha256:e8549c143af3ce6559699a01e26fa4174f4c591dbee0a499f3cd4c3781cdec3d", - "sha256:903a7b87b74635244548b30d30db4c8947fe64c5198f58899ddcd3a13c23bb26" - ], - "version": "==0.12.2" - } - }, - "develop": {} -} diff --git a/README.md b/README.md index 78ef5390..6da52cff 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ This is the source code for my post on [Building a Blockchain](https://medium.co ## Installation +### Python + 1. Make sure [Python 3.6+](https://www.python.org/downloads/) is installed. 2. Install [pipenv](https://github.com/kennethreitz/pipenv). @@ -18,11 +20,11 @@ $ pipenv install ``` 4. Run the server: - * `$ pipenv run python blockchain.py` - * `$ pipenv run python blockchain.py -p 5001` - * `$ pipenv run python blockchain.py --port 5002` + * `$ pipenv run python api.py` + * `$ pipenv run python api.py -p 5001` + * `$ pipenv run python api.py --port 5002` -## Docker +#### Docker Another option for running this blockchain program is to use Docker. Follow the instructions below to create a local Docker container: @@ -47,18 +49,14 @@ $ docker run --rm -p 82:5000 blockchain $ docker run --rm -p 83:5000 blockchain ``` -## Installation (C# Implementation) +### C# 1. Install a free copy of Visual Studio IDE (Community Edition): https://www.visualstudio.com/vs/ - 2. Once installed, open the solution file (BlockChain.sln) using the File > Open > Project/Solution menu options within Visual Studio. - 3. From within the "Solution Explorer", right click the BlockChain.Console project and select the "Set As Startup Project" option. - 4. Click the "Start" button, or hit F5 to run. The program executes in a console window, and is controlled via HTTP with the same commands as the Python version. - ## Contributing Contributions are welcome! Please feel free to submit a Pull Request. diff --git a/.travis.yml b/python/.travis.yml similarity index 71% rename from .travis.yml rename to python/.travis.yml index 1991e5d4..90cb158c 100644 --- a/.travis.yml +++ b/python/.travis.yml @@ -1,7 +1,7 @@ language: python python: - - 3.6 + - 3.9 - nightly install: @@ -9,4 +9,4 @@ install: - pipenv install --dev script: - - pipenv run python -m unittest + - pipenv run python -m unittest \ No newline at end of file diff --git a/Dockerfile b/python/Dockerfile similarity index 68% rename from Dockerfile rename to python/Dockerfile index 82de3243..45bc0dc5 100644 --- a/Dockerfile +++ b/python/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.6-alpine +FROM python:3.9-alpine WORKDIR /app @@ -9,7 +9,8 @@ RUN cd /app && \ # Add actual source code. ADD blockchain.py /app +ADD api.py /app EXPOSE 5000 -CMD ["python", "blockchain.py", "--port", "5000"] +CMD ["python", "api.py", "--port", "5000"] diff --git a/Pipfile b/python/Pipfile similarity index 65% rename from Pipfile rename to python/Pipfile index 31c3e0c3..0159e489 100644 --- a/Pipfile +++ b/python/Pipfile @@ -6,10 +6,9 @@ name = "pypi" [dev-packages] [requires] -python_version = "3.6" +python_version = "3.9" [packages] -flask = "==0.12.2" -requests = "==2.18.4" - +flask = "latest" +requests = "latest" \ No newline at end of file diff --git a/python/Pipfile.lock b/python/Pipfile.lock new file mode 100644 index 00000000..da67c106 --- /dev/null +++ b/python/Pipfile.lock @@ -0,0 +1,138 @@ +{ + "_meta": { + "hash": { + "sha256": "753444cccb70e7172dbb540425e75ff7b486857adc7a4baf11852e947b78c99e" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.9" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.python.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "certifi": { + "hashes": [ + "sha256:1f422849db327d534e3d0c5f02a263458c3955ec0aae4ff09b95f195c59f4edd", + "sha256:f05def092c44fbf25834a51509ef6e631dc19765ab8a57b4e7ab85531f0a9cf4" + ], + "version": "==2020.11.8" + }, + "chardet": { + "hashes": [ + "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", + "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" + ], + "version": "==3.0.4" + }, + "click": { + "hashes": [ + "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", + "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==7.1.2" + }, + "flask": { + "hashes": [ + "sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060", + "sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557" + ], + "index": "pypi", + "version": "==1.1.2" + }, + "idna": { + "hashes": [ + "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", + "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.10" + }, + "itsdangerous": { + "hashes": [ + "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19", + "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.1.0" + }, + "jinja2": { + "hashes": [ + "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0", + "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==2.11.2" + }, + "markupsafe": { + "hashes": [ + "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", + "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", + "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", + "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", + "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42", + "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", + "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", + "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", + "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", + "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", + "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", + "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b", + "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", + "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15", + "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", + "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", + "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", + "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", + "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", + "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", + "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", + "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", + "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", + "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", + "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", + "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", + "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", + "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", + "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", + "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", + "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2", + "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7", + "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.1.1" + }, + "requests": { + "hashes": [ + "sha256:7f1a0b932f4a60a1a65caa4263921bb7d9ee911957e0ae4a23a6dd08185ad5f8", + "sha256:e786fa28d8c9154e6a4de5d46a1d921b8749f8b74e28bde23768e5e16eece998" + ], + "index": "pypi", + "version": "==2.25.0" + }, + "urllib3": { + "hashes": [ + "sha256:19188f96923873c92ccb987120ec4acaa12f0461fa9ce5d3d0772bc965a39e08", + "sha256:d8ff90d979214d7b4f8ce956e80f4028fc6860e4431f731ea4a8c08f23f99473" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", + "version": "==1.26.2" + }, + "werkzeug": { + "hashes": [ + "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43", + "sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==1.0.1" + } + }, + "develop": {} +} diff --git a/python/api.py b/python/api.py new file mode 100644 index 00000000..c3f19bd2 --- /dev/null +++ b/python/api.py @@ -0,0 +1,114 @@ +import requests +from flask import Flask, jsonify, request +from blockchain import Blockchain +from uuid import uuid4 + +# Instantiate the Node +app = Flask(__name__) +app.config['JSONIFY_PRETTYPRINT_REGULAR'] = False + +# Generate a globally unique address for this node +node_identifier = str(uuid4()).replace('-', '') + +# Instantiate the Blockchain +blockchain = Blockchain() + + +@app.route('/mine', methods=['GET']) +def mine(): + # We run the proof of work algorithm to get the next proof... + last_block = blockchain.last_block + proof = blockchain.proof_of_work(last_block) + + # We must receive a reward for finding the proof. + # The sender is "0" to signify that this node has mined a new coin. + blockchain.new_transaction( + sender="0", + recipient=node_identifier, + amount=1, + ) + + # Forge the new Block by adding it to the chain + previous_hash = blockchain.hash(last_block) + block = blockchain.new_block(proof, previous_hash) + + response = { + 'message': "New Block Forged", + 'index': block['index'], + 'transactions': block['transactions'], + 'proof': block['proof'], + 'previous_hash': block['previous_hash'], + } + return jsonify(response), 200 + + +@app.route('/transactions/new', methods=['POST']) +def new_transaction(): + values = request.get_json() + + # Check that the required fields are in the POST'ed data + required = ['sender', 'recipient', 'amount'] + if not all(k in values for k in required): + return 'Missing values', 400 + + # Create a new Transaction + index = blockchain.new_transaction(values['sender'], values['recipient'], values['amount']) + + response = {'message': f'Transaction will be added to Block {index}'} + return jsonify(response), 201 + + +@app.route('/chain', methods=['GET']) +def full_chain(): + response = { + 'chain': blockchain.chain, + 'length': len(blockchain.chain), + } + return jsonify(response), 200 + + +@app.route('/nodes/register', methods=['POST']) +def register_nodes(): + values = request.get_json() + + nodes = values.get('nodes') + if nodes is None: + return "Error: Please supply a valid list of nodes", 400 + + for node in nodes: + blockchain.register_node(node) + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return jsonify(response), 201 + + +@app.route('/nodes/resolve', methods=['GET']) +def consensus(): + replaced = blockchain.resolve_conflicts() + + if replaced: + response = { + 'message': 'Our chain was replaced', + 'new_chain': blockchain.chain + } + else: + response = { + 'message': 'Our chain is authoritative', + 'chain': blockchain.chain + } + + return jsonify(response), 200 + + +if __name__ == '__main__': + from argparse import ArgumentParser + + parser = ArgumentParser() + parser.add_argument('-p', '--port', default=5000, type=int, help='port to listen on') + args = parser.parse_args() + port = args.port + + app.run(host='0.0.0.0', port=port) diff --git a/blockchain.py b/python/blockchain.py similarity index 64% rename from blockchain.py rename to python/blockchain.py index 937d3521..2788322b 100644 --- a/blockchain.py +++ b/python/blockchain.py @@ -2,10 +2,6 @@ import json from time import time from urllib.parse import urlparse -from uuid import uuid4 - -import requests -from flask import Flask, jsonify, request class Blockchain: @@ -188,114 +184,4 @@ def valid_proof(last_proof, proof, last_hash): guess = f'{last_proof}{proof}{last_hash}'.encode() guess_hash = hashlib.sha256(guess).hexdigest() - return guess_hash[:4] == "0000" - - -# Instantiate the Node -app = Flask(__name__) - -# Generate a globally unique address for this node -node_identifier = str(uuid4()).replace('-', '') - -# Instantiate the Blockchain -blockchain = Blockchain() - - -@app.route('/mine', methods=['GET']) -def mine(): - # We run the proof of work algorithm to get the next proof... - last_block = blockchain.last_block - proof = blockchain.proof_of_work(last_block) - - # We must receive a reward for finding the proof. - # The sender is "0" to signify that this node has mined a new coin. - blockchain.new_transaction( - sender="0", - recipient=node_identifier, - amount=1, - ) - - # Forge the new Block by adding it to the chain - previous_hash = blockchain.hash(last_block) - block = blockchain.new_block(proof, previous_hash) - - response = { - 'message': "New Block Forged", - 'index': block['index'], - 'transactions': block['transactions'], - 'proof': block['proof'], - 'previous_hash': block['previous_hash'], - } - return jsonify(response), 200 - - -@app.route('/transactions/new', methods=['POST']) -def new_transaction(): - values = request.get_json() - - # Check that the required fields are in the POST'ed data - required = ['sender', 'recipient', 'amount'] - if not all(k in values for k in required): - return 'Missing values', 400 - - # Create a new Transaction - index = blockchain.new_transaction(values['sender'], values['recipient'], values['amount']) - - response = {'message': f'Transaction will be added to Block {index}'} - return jsonify(response), 201 - - -@app.route('/chain', methods=['GET']) -def full_chain(): - response = { - 'chain': blockchain.chain, - 'length': len(blockchain.chain), - } - return jsonify(response), 200 - - -@app.route('/nodes/register', methods=['POST']) -def register_nodes(): - values = request.get_json() - - nodes = values.get('nodes') - if nodes is None: - return "Error: Please supply a valid list of nodes", 400 - - for node in nodes: - blockchain.register_node(node) - - response = { - 'message': 'New nodes have been added', - 'total_nodes': list(blockchain.nodes), - } - return jsonify(response), 201 - - -@app.route('/nodes/resolve', methods=['GET']) -def consensus(): - replaced = blockchain.resolve_conflicts() - - if replaced: - response = { - 'message': 'Our chain was replaced', - 'new_chain': blockchain.chain - } - else: - response = { - 'message': 'Our chain is authoritative', - 'chain': blockchain.chain - } - - return jsonify(response), 200 - - -if __name__ == '__main__': - from argparse import ArgumentParser - - parser = ArgumentParser() - parser.add_argument('-p', '--port', default=5000, type=int, help='port to listen on') - args = parser.parse_args() - port = args.port - - app.run(host='0.0.0.0', port=port) + return guess_hash[:4] == "0000" \ No newline at end of file diff --git a/python/requirements.txt b/python/requirements.txt new file mode 100644 index 00000000..5eaf725f --- /dev/null +++ b/python/requirements.txt @@ -0,0 +1,2 @@ +flask +requests \ No newline at end of file diff --git a/tests/__init__.py b/python/tests/__init__.py similarity index 100% rename from tests/__init__.py rename to python/tests/__init__.py diff --git a/tests/test_blockchain.py b/python/tests/test_blockchain.py similarity index 100% rename from tests/test_blockchain.py rename to python/tests/test_blockchain.py diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 990b2d8a..00000000 --- a/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -flask==0.12.2 -requests==2.18.4