From 98c070af9cb430bf432553afb89269cd857d10a7 Mon Sep 17 00:00:00 2001 From: Daniel van Flymen Date: Sun, 7 Apr 2019 23:05:39 -0400 Subject: [PATCH] Clean up --- Dockerfile | 15 - LICENSE | 21 -- Pipfile | 15 - Pipfile.lock | 109 ------ blockchain.py | 313 +++--------------- csharp/BlockChain.Console/App.config | 10 - .../BlockChain.Console.csproj | 63 ---- csharp/BlockChain.Console/Program.cs | 12 - .../Properties/AssemblyInfo.cs | 36 -- csharp/BlockChain.sln | 43 --- csharp/BlockChain/Block.cs | 19 -- csharp/BlockChain/BlockChain.cs | 226 ------------- csharp/BlockChain/BlockChain.csproj | 73 ---- csharp/BlockChain/Node.cs | 9 - csharp/BlockChain/Properties/AssemblyInfo.cs | 36 -- csharp/BlockChain/Transaction.cs | 9 - csharp/BlockChain/WebServer.cs | 78 ----- csharp/BlockChain/packages.config | 5 - requirements.txt | 3 +- tests/test_blockchain.py | 121 ++----- 20 files changed, 75 insertions(+), 1141 deletions(-) delete mode 100644 Dockerfile delete mode 100644 LICENSE delete mode 100644 Pipfile delete mode 100644 Pipfile.lock delete mode 100644 csharp/BlockChain.Console/App.config delete mode 100644 csharp/BlockChain.Console/BlockChain.Console.csproj delete mode 100644 csharp/BlockChain.Console/Program.cs delete mode 100644 csharp/BlockChain.Console/Properties/AssemblyInfo.cs delete mode 100644 csharp/BlockChain.sln delete mode 100644 csharp/BlockChain/Block.cs delete mode 100644 csharp/BlockChain/BlockChain.cs delete mode 100644 csharp/BlockChain/BlockChain.csproj delete mode 100644 csharp/BlockChain/Node.cs delete mode 100644 csharp/BlockChain/Properties/AssemblyInfo.cs delete mode 100644 csharp/BlockChain/Transaction.cs delete mode 100644 csharp/BlockChain/WebServer.cs delete mode 100644 csharp/BlockChain/packages.config diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 82de324..0000000 --- a/Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -FROM python:3.6-alpine - -WORKDIR /app - -# Install dependencies. -ADD requirements.txt /app -RUN cd /app && \ - pip install -r requirements.txt - -# Add actual source code. -ADD blockchain.py /app - -EXPOSE 5000 - -CMD ["python", "blockchain.py", "--port", "5000"] diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 10b5362..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2017 Daniel van Flymen - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/Pipfile b/Pipfile deleted file mode 100644 index 31c3e0c..0000000 --- a/Pipfile +++ /dev/null @@ -1,15 +0,0 @@ -[[source]] -url = "https://pypi.python.org/simple" -verify_ssl = true -name = "pypi" - -[dev-packages] - -[requires] -python_version = "3.6" - -[packages] - -flask = "==0.12.2" -requests = "==2.18.4" - diff --git a/Pipfile.lock b/Pipfile.lock deleted file mode 100644 index 4d21bf7..0000000 --- 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/blockchain.py b/blockchain.py index 937d352..a0a7478 100644 --- a/blockchain.py +++ b/blockchain.py @@ -1,301 +1,66 @@ -import hashlib import json -from time import time -from urllib.parse import urlparse -from uuid import uuid4 +import os -import requests -from flask import Flask, jsonify, request +from datetime import datetime +from hashlib import sha256, md5 -class Blockchain: +class Blockchain(object): def __init__(self): - self.current_transactions = [] self.chain = [] - self.nodes = set() + self.pending_transactions = [] # Create the genesis block - self.new_block(previous_hash='1', proof=100) + print("Creating genesis block") - def register_node(self, address): - """ - Add a new node to the list of nodes - - :param address: Address of node. Eg. 'http://192.168.0.5:5000' - """ - - parsed_url = urlparse(address) - if parsed_url.netloc: - self.nodes.add(parsed_url.netloc) - elif parsed_url.path: - # Accepts an URL without scheme like '192.168.0.5:5000'. - self.nodes.add(parsed_url.path) - else: - raise ValueError('Invalid URL') - - - def valid_chain(self, chain): - """ - Determine if a given blockchain is valid - - :param chain: A blockchain - :return: True if valid, False if not - """ - - last_block = chain[0] - current_index = 1 - - while current_index < len(chain): - block = chain[current_index] - print(f'{last_block}') - print(f'{block}') - print("\n-----------\n") - # Check that the hash of the block is correct - last_block_hash = self.hash(last_block) - if block['previous_hash'] != last_block_hash: - return False - - # Check that the Proof of Work is correct - if not self.valid_proof(last_block['proof'], block['proof'], last_block_hash): - return False - - last_block = block - current_index += 1 - - return True - - def resolve_conflicts(self): - """ - This is our consensus algorithm, it resolves conflicts - by replacing our chain with the longest one in the network. - - :return: True if our chain was replaced, False if not - """ - - neighbours = self.nodes - new_chain = None - - # We're only looking for chains longer than ours - max_length = len(self.chain) - - # Grab and verify the chains from all the nodes in our network - for node in neighbours: - response = requests.get(f'http://{node}/chain') - - if response.status_code == 200: - length = response.json()['length'] - chain = response.json()['chain'] - - # Check if the length is longer and the chain is valid - if length > max_length and self.valid_chain(chain): - max_length = length - new_chain = chain - - # Replace our chain if we discovered a new, valid chain longer than ours - if new_chain: - self.chain = new_chain - return True - - return False - - def new_block(self, proof, previous_hash): - """ - Create a new Block in the Blockchain - - :param proof: The proof given by the Proof of Work algorithm - :param previous_hash: Hash of previous Block - :return: New Block - """ + self.new_block() + def new_block(self, previous_hash=None, nonce=None): block = { - 'index': len(self.chain) + 1, - 'timestamp': time(), - 'transactions': self.current_transactions, - 'proof': proof, - 'previous_hash': previous_hash or self.hash(self.chain[-1]), + 'index': len(self.chain), + 'timestamp': datetime.utcnow().isoformat(), + 'transactions': self.pending_transactions, + 'previous_hash': previous_hash, + 'nonce': nonce, } - # Reset the current list of transactions - self.current_transactions = [] + # Get the hash of this new block, and add it to the block + block["hash"] = self.hash(block) + print(f"Created block {block['index']}") + + # Add the block to the chain self.chain.append(block) + + # Reset the list of pending transactions + self.pending_transactions = [] return block - def new_transaction(self, sender, recipient, amount): - """ - Creates a new transaction to go into the next mined Block - - :param sender: Address of the Sender - :param recipient: Address of the Recipient - :param amount: Amount - :return: The index of the Block that will hold this transaction - """ - self.current_transactions.append({ - 'sender': sender, - 'recipient': recipient, - 'amount': amount, - }) - - return self.last_block['index'] + 1 - - @property - def last_block(self): - return self.chain[-1] - @staticmethod def hash(block): - """ - Creates a SHA-256 hash of a Block - - :param block: Block - """ - - # We must make sure that the Dictionary is Ordered, or we'll have inconsistent hashes + # Transform the block dict into a json string with sorted keys block_string = json.dumps(block, sort_keys=True).encode() - return hashlib.sha256(block_string).hexdigest() - def proof_of_work(self, last_block): - """ - Simple Proof of Work Algorithm: + # ... and hash it + return sha256(block_string).hexdigest() - - Find a number p' such that hash(pp') contains leading 4 zeroes - - Where p is the previous proof, and p' is the new proof - - :param last_block: last Block - :return: - """ - - last_proof = last_block['proof'] - last_hash = self.hash(last_block) - - proof = 0 - while self.valid_proof(last_proof, proof, last_hash) is False: - proof += 1 - - return proof + def last_block(self): + # Returns the last block in the chain (if there are blocks) + return self.chain[-1] if self.chain else None @staticmethod - def valid_proof(last_proof, proof, last_hash): - """ - Validates the Proof + def pow_is_acceptable(hash_of_block, difficulty): + return hash_of_block[:difficulty] == "0" * difficulty - :param last_proof: Previous Proof - :param proof: Current Proof - :param last_hash: The hash of the Previous Block - :return: True if correct, False if not. + @staticmethod + def nonce(): + return sha256(os.urandom(16)).hexdigest() - """ + def proof_of_work(self, block=None, difficulty=4): + block = block or self.last_block() - 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) + while True: + block["nonce"] = self.nonce() + if self.pow_is_acceptable(hash_of_block=self.hash(block), difficulty=difficulty): + print(f"Block hash is {self.hash(block)} with random string {block['nonce']}") + return block diff --git a/csharp/BlockChain.Console/App.config b/csharp/BlockChain.Console/App.config deleted file mode 100644 index 089f249..0000000 --- a/csharp/BlockChain.Console/App.config +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/csharp/BlockChain.Console/BlockChain.Console.csproj b/csharp/BlockChain.Console/BlockChain.Console.csproj deleted file mode 100644 index 5bc5670..0000000 --- a/csharp/BlockChain.Console/BlockChain.Console.csproj +++ /dev/null @@ -1,63 +0,0 @@ - - - - - Debug - AnyCPU - {D0C795A0-6F20-4A8E-BE44-801678754DA4} - Exe - BlockChainDemo.Console - BlockChainDemo.Console - v4.5.1 - 512 - true - SAK - SAK - SAK - SAK - - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - - - - - - - - - - {e06fc4ce-77d0-4a64-94a6-32a08920e481} - BlockChain - - - - \ No newline at end of file diff --git a/csharp/BlockChain.Console/Program.cs b/csharp/BlockChain.Console/Program.cs deleted file mode 100644 index 2573f93..0000000 --- a/csharp/BlockChain.Console/Program.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace BlockChainDemo.Console -{ - class Program - { - static void Main(string[] args) - { - var chain = new BlockChain(); - var server = new WebServer(chain); - System.Console.Read(); - } - } -} diff --git a/csharp/BlockChain.Console/Properties/AssemblyInfo.cs b/csharp/BlockChain.Console/Properties/AssemblyInfo.cs deleted file mode 100644 index 2b25de2..0000000 --- a/csharp/BlockChain.Console/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("BlockChain.Console")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("BlockChain.Console")] -[assembly: AssemblyCopyright("Copyright © 2017")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("d0c795a0-6f20-4a8e-be44-801678754da4")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/csharp/BlockChain.sln b/csharp/BlockChain.sln deleted file mode 100644 index 0175024..0000000 --- a/csharp/BlockChain.sln +++ /dev/null @@ -1,43 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27004.2008 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlockChain", "BlockChain\BlockChain.csproj", "{E06FC4CE-77D0-4A64-94A6-32A08920E481}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlockChain.Console", "BlockChain.Console\BlockChain.Console.csproj", "{D0C795A0-6F20-4A8E-BE44-801678754DA4}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {E06FC4CE-77D0-4A64-94A6-32A08920E481}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E06FC4CE-77D0-4A64-94A6-32A08920E481}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E06FC4CE-77D0-4A64-94A6-32A08920E481}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E06FC4CE-77D0-4A64-94A6-32A08920E481}.Release|Any CPU.Build.0 = Release|Any CPU - {D0C795A0-6F20-4A8E-BE44-801678754DA4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D0C795A0-6F20-4A8E-BE44-801678754DA4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D0C795A0-6F20-4A8E-BE44-801678754DA4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D0C795A0-6F20-4A8E-BE44-801678754DA4}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {7FB3650B-CAFB-4A71-940B-0CA6F0377422} - EndGlobalSection - GlobalSection(TeamFoundationVersionControl) = preSolution - SccNumberOfProjects = 3 - SccEnterpriseProvider = {4CA58AB2-18FA-4F8D-95D4-32DDF27D184C} - SccTeamFoundationServer = https://devfire.visualstudio.com/ - SccLocalPath0 = . - SccProjectUniqueName1 = BlockChain\\BlockChain.csproj - SccProjectName1 = BlockChain - SccLocalPath1 = BlockChain - SccProjectUniqueName2 = BlockChain.Console\\BlockChain.Console.csproj - SccProjectName2 = BlockChain.Console - SccLocalPath2 = BlockChain.Console - EndGlobalSection -EndGlobal diff --git a/csharp/BlockChain/Block.cs b/csharp/BlockChain/Block.cs deleted file mode 100644 index 9ca9d80..0000000 --- a/csharp/BlockChain/Block.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace BlockChainDemo -{ - public class Block - { - public int Index { get; set; } - public DateTime Timestamp { get; set; } - public List Transactions { get; set; } - public int Proof { get; set; } - public string PreviousHash { get; set; } - - public override string ToString() - { - return $"{Index} [{Timestamp.ToString("yyyy-MM-dd HH:mm:ss")}] Proof: {Proof} | PrevHash: {PreviousHash} | Trx: {Transactions.Count}"; - } - } -} \ No newline at end of file diff --git a/csharp/BlockChain/BlockChain.cs b/csharp/BlockChain/BlockChain.cs deleted file mode 100644 index 250d6f8..0000000 --- a/csharp/BlockChain/BlockChain.cs +++ /dev/null @@ -1,226 +0,0 @@ -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Net; -using System.Security.Cryptography; -using System.Text; - -namespace BlockChainDemo -{ - public class BlockChain - { - private List _currentTransactions = new List(); - private List _chain = new List(); - private List _nodes = new List(); - private Block _lastBlock => _chain.Last(); - - public string NodeId { get; private set; } - - //ctor - public BlockChain() - { - NodeId = Guid.NewGuid().ToString().Replace("-", ""); - CreateNewBlock(proof: 100, previousHash: "1"); //genesis block - } - - //private functionality - private void RegisterNode(string address) - { - _nodes.Add(new Node { Address = new Uri(address) }); - } - - private bool IsValidChain(List chain) - { - Block block = null; - Block lastBlock = chain.First(); - int currentIndex = 1; - while (currentIndex < chain.Count) - { - block = chain.ElementAt(currentIndex); - Debug.WriteLine($"{lastBlock}"); - Debug.WriteLine($"{block}"); - Debug.WriteLine("----------------------------"); - - //Check that the hash of the block is correct - if (block.PreviousHash != GetHash(lastBlock)) - return false; - - //Check that the Proof of Work is correct - if (!IsValidProof(lastBlock.Proof, block.Proof, lastBlock.PreviousHash)) - return false; - - lastBlock = block; - currentIndex++; - } - - return true; - } - - private bool ResolveConflicts() - { - List newChain = null; - int maxLength = _chain.Count; - - foreach (Node node in _nodes) - { - var url = new Uri(node.Address, "/chain"); - var request = (HttpWebRequest)WebRequest.Create(url); - var response = (HttpWebResponse)request.GetResponse(); - - if (response.StatusCode == HttpStatusCode.OK) - { - var model = new - { - chain = new List(), - length = 0 - }; - string json = new StreamReader(response.GetResponseStream()).ReadToEnd(); - var data = JsonConvert.DeserializeAnonymousType(json, model); - - if (data.chain.Count > _chain.Count && IsValidChain(data.chain)) - { - maxLength = data.chain.Count; - newChain = data.chain; - } - } - } - - if (newChain != null) - { - _chain = newChain; - return true; - } - - return false; - } - - private Block CreateNewBlock(int proof, string previousHash = null) - { - var block = new Block - { - Index = _chain.Count, - Timestamp = DateTime.UtcNow, - Transactions = _currentTransactions.ToList(), - Proof = proof, - PreviousHash = previousHash ?? GetHash(_chain.Last()) - }; - - _currentTransactions.Clear(); - _chain.Add(block); - return block; - } - - private int CreateProofOfWork(int lastProof, string previousHash) - { - int proof = 0; - while (!IsValidProof(lastProof, proof, previousHash)) - proof++; - - return proof; - } - - private bool IsValidProof(int lastProof, int proof, string previousHash) - { - string guess = $"{lastProof}{proof}{previousHash}"; - string result = GetSha256(guess); - return result.StartsWith("0000"); - } - - private string GetHash(Block block) - { - string blockText = JsonConvert.SerializeObject(block); - return GetSha256(blockText); - } - - private string GetSha256(string data) - { - var sha256 = new SHA256Managed(); - var hashBuilder = new StringBuilder(); - - byte[] bytes = Encoding.Unicode.GetBytes(data); - byte[] hash = sha256.ComputeHash(bytes); - - foreach (byte x in hash) - hashBuilder.Append($"{x:x2}"); - - return hashBuilder.ToString(); - } - - //web server calls - internal string Mine() - { - int proof = CreateProofOfWork(_lastBlock.Proof, _lastBlock.PreviousHash); - - CreateTransaction(sender: "0", recipient: NodeId, amount: 1); - Block block = CreateNewBlock(proof /*, _lastBlock.PreviousHash*/); - - var response = new - { - Message = "New Block Forged", - Index = block.Index, - Transactions = block.Transactions.ToArray(), - Proof = block.Proof, - PreviousHash = block.PreviousHash - }; - - return JsonConvert.SerializeObject(response); - } - - internal string GetFullChain() - { - var response = new - { - chain = _chain.ToArray(), - length = _chain.Count - }; - - return JsonConvert.SerializeObject(response); - } - - internal string RegisterNodes(string[] nodes) - { - var builder = new StringBuilder(); - foreach (string node in nodes) - { - string url = $"http://{node}"; - RegisterNode(url); - builder.Append($"{url}, "); - } - - builder.Insert(0, $"{nodes.Count()} new nodes have been added: "); - string result = builder.ToString(); - return result.Substring(0, result.Length - 2); - } - - internal string Consensus() - { - bool replaced = ResolveConflicts(); - string message = replaced ? "was replaced" : "is authoritive"; - - var response = new - { - Message = $"Our chain {message}", - Chain = _chain - }; - - return JsonConvert.SerializeObject(response); - } - - internal int CreateTransaction(string sender, string recipient, int amount) - { - var transaction = new Transaction - { - Sender = sender, - Recipient = recipient, - Amount = amount - }; - - _currentTransactions.Add(transaction); - - return _lastBlock != null ? _lastBlock.Index + 1 : 0; - } - } -} diff --git a/csharp/BlockChain/BlockChain.csproj b/csharp/BlockChain/BlockChain.csproj deleted file mode 100644 index 86681ac..0000000 --- a/csharp/BlockChain/BlockChain.csproj +++ /dev/null @@ -1,73 +0,0 @@ - - - - - Debug - AnyCPU - {E06FC4CE-77D0-4A64-94A6-32A08920E481} - Library - Properties - BlockChainDemo - BlockChainDemo - v4.5.1 - 512 - SAK - SAK - SAK - SAK - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll - - - - - - - - - - - - ..\packages\TinyWebServer.dll.1.0.1\lib\net40\TinyWebServer.dll - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/csharp/BlockChain/Node.cs b/csharp/BlockChain/Node.cs deleted file mode 100644 index 5ba425f..0000000 --- a/csharp/BlockChain/Node.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -namespace BlockChainDemo -{ - public class Node - { - public Uri Address { get; set; } - } -} \ No newline at end of file diff --git a/csharp/BlockChain/Properties/AssemblyInfo.cs b/csharp/BlockChain/Properties/AssemblyInfo.cs deleted file mode 100644 index 87b9d4e..0000000 --- a/csharp/BlockChain/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("BlockChain")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("BlockChain")] -[assembly: AssemblyCopyright("Copyright © 2017")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("e06fc4ce-77d0-4a64-94a6-32a08920e481")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/csharp/BlockChain/Transaction.cs b/csharp/BlockChain/Transaction.cs deleted file mode 100644 index 1cbaeb7..0000000 --- a/csharp/BlockChain/Transaction.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace BlockChainDemo -{ - public class Transaction - { - public int Amount { get; set; } - public string Recipient { get; set; } - public string Sender { get; set; } - } -} \ No newline at end of file diff --git a/csharp/BlockChain/WebServer.cs b/csharp/BlockChain/WebServer.cs deleted file mode 100644 index cd7b86b..0000000 --- a/csharp/BlockChain/WebServer.cs +++ /dev/null @@ -1,78 +0,0 @@ -using Newtonsoft.Json; -using System.Configuration; -using System.IO; -using System.Net; -using System.Net.Http; - -namespace BlockChainDemo -{ - public class WebServer - { - public WebServer(BlockChain chain) - { - var settings = ConfigurationManager.AppSettings; - string host = settings["host"]?.Length > 1 ? settings["host"] : "localhost"; - string port = settings["port"]?.Length > 1 ? settings["port"] : "12345"; - - var server = new TinyWebServer.WebServer(request => - { - string path = request.Url.PathAndQuery.ToLower(); - string query = ""; - string json = ""; - if (path.Contains("?")) - { - string[] parts = path.Split('?'); - path = parts[0]; - query = parts[1]; - } - - switch (path) - { - //GET: http://localhost:12345/mine - case "/mine": - return chain.Mine(); - - //POST: http://localhost:12345/transactions/new - //{ "Amount":123, "Recipient":"ebeabf5cc1d54abdbca5a8fe9493b479", "Sender":"31de2e0ef1cb4937830fcfd5d2b3b24f" } - case "/transactions/new": - if (request.HttpMethod != HttpMethod.Post.Method) - return $"{new HttpResponseMessage(HttpStatusCode.MethodNotAllowed)}"; - - json = new StreamReader(request.InputStream).ReadToEnd(); - Transaction trx = JsonConvert.DeserializeObject(json); - int blockId = chain.CreateTransaction(trx.Sender, trx.Recipient, trx.Amount); - return $"Your transaction will be included in block {blockId}"; - - //GET: http://localhost:12345/chain - case "/chain": - return chain.GetFullChain(); - - //POST: http://localhost:12345/nodes/register - //{ "Urls": ["localhost:54321", "localhost:54345", "localhost:12321"] } - case "/nodes/register": - if (request.HttpMethod != HttpMethod.Post.Method) - return $"{new HttpResponseMessage(HttpStatusCode.MethodNotAllowed)}"; - - json = new StreamReader(request.InputStream).ReadToEnd(); - var urlList = new { Urls = new string[0] }; - var obj = JsonConvert.DeserializeAnonymousType(json, urlList); - return chain.RegisterNodes(obj.Urls); - - //GET: http://localhost:12345/nodes/resolve - case "/nodes/resolve": - return chain.Consensus(); - } - - return ""; - }, - $"http://{host}:{port}/mine/", - $"http://{host}:{port}/transactions/new/", - $"http://{host}:{port}/chain/", - $"http://{host}:{port}/nodes/register/", - $"http://{host}:{port}/nodes/resolve/" - ); - - server.Run(); - } - } -} diff --git a/csharp/BlockChain/packages.config b/csharp/BlockChain/packages.config deleted file mode 100644 index 4c8c064..0000000 --- a/csharp/BlockChain/packages.config +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 990b2d8..cbd34f7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ -flask==0.12.2 +flask==1.0.2 requests==2.18.4 +pytest==4.4.0 diff --git a/tests/test_blockchain.py b/tests/test_blockchain.py index f2d85e1..5c8f154 100644 --- a/tests/test_blockchain.py +++ b/tests/test_blockchain.py @@ -1,104 +1,51 @@ -import hashlib -import json -from unittest import TestCase +import pytest from blockchain import Blockchain -class BlockchainTestCase(TestCase): - - def setUp(self): - self.blockchain = Blockchain() - - def create_block(self, proof=123, previous_hash='abc'): - self.blockchain.new_block(proof, previous_hash) - - def create_transaction(self, sender='a', recipient='b', amount=1): - self.blockchain.new_transaction( - sender=sender, - recipient=recipient, - amount=amount - ) +@pytest.fixture +def blockchain(): + return Blockchain() -class TestRegisterNodes(BlockchainTestCase): +class TestBlockchain: + def test_genesis_block(self, blockchain): + genesis_block = blockchain.chain[0] - def test_valid_nodes(self): - blockchain = Blockchain() + assert len(blockchain.chain) == 1 + assert genesis_block["index"] == 0 + assert genesis_block["transactions"] == [] + assert genesis_block["timestamp"] + assert genesis_block["previous_hash"] is None + assert genesis_block["nonce"] is None + assert genesis_block["hash"] - blockchain.register_node('http://192.168.0.1:5000') + def test_pending_transactions_reset_after_block_addition(self, blockchain): + blockchain.pending_transactions.append({"from": "Alice", "to": "Bob", "amount": 12}) + blockchain.new_block() - self.assertIn('192.168.0.1:5000', blockchain.nodes) + assert blockchain.pending_transactions == [] - def test_malformed_nodes(self): - blockchain = Blockchain() + def test_blocks_are_hashed_correctly(self, blockchain): + some_block = blockchain.new_block(previous_hash="abc123", nonce="abc123") - blockchain.register_node('http//192.168.0.1:5000') + assert some_block["hash"] == blockchain.hash({k: v for k, v in some_block.items() if k != "hash"}) - self.assertNotIn('192.168.0.1:5000', blockchain.nodes) + def test_blockchain_returns_last_block(self, blockchain): + some_block = blockchain.new_block(previous_hash="abc123", nonce="abc123") - def test_idempotency(self): - blockchain = Blockchain() + assert blockchain.last_block() == some_block - blockchain.register_node('http://192.168.0.1:5000') - blockchain.register_node('http://192.168.0.1:5000') + def test_pow_is_acceptable(self, blockchain): + assert blockchain.pow_is_acceptable("00000acbdef123123987hsdkfjskdf213", 5) is True + assert blockchain.pow_is_acceptable("0000acbdef123123987hsdkfjskdf2134", 5) is False - assert len(blockchain.nodes) == 1 + def test_nonce(self, blockchain): + assert blockchain.nonce() + assert blockchain.nonce() != blockchain.nonce() + def test_proof_of_work(self, blockchain): + mined_block = blockchain.proof_of_work(difficulty=3) -class TestBlocksAndTransactions(BlockchainTestCase): - - def test_block_creation(self): - self.create_block() - - latest_block = self.blockchain.last_block - - # The genesis block is create at initialization, so the length should be 2 - assert len(self.blockchain.chain) == 2 - assert latest_block['index'] == 2 - assert latest_block['timestamp'] is not None - assert latest_block['proof'] == 123 - assert latest_block['previous_hash'] == 'abc' - - def test_create_transaction(self): - self.create_transaction() - - transaction = self.blockchain.current_transactions[-1] - - assert transaction - assert transaction['sender'] == 'a' - assert transaction['recipient'] == 'b' - assert transaction['amount'] == 1 - - def test_block_resets_transactions(self): - self.create_transaction() - - initial_length = len(self.blockchain.current_transactions) - - self.create_block() - - current_length = len(self.blockchain.current_transactions) - - assert initial_length == 1 - assert current_length == 0 - - def test_return_last_block(self): - self.create_block() - - created_block = self.blockchain.last_block - - assert len(self.blockchain.chain) == 2 - assert created_block is self.blockchain.chain[-1] - - -class TestHashingAndProofs(BlockchainTestCase): - - def test_hash_is_correct(self): - self.create_block() - - new_block = self.blockchain.last_block - new_block_json = json.dumps(self.blockchain.last_block, sort_keys=True).encode() - new_hash = hashlib.sha256(new_block_json).hexdigest() - - assert len(new_hash) == 64 - assert new_hash == self.blockchain.hash(new_block) + assert mined_block["nonce"] + assert blockchain.hash(mined_block)[:3] == "000"