Clean up
This commit is contained in:
parent
28d3ee8c16
commit
98c070af9c
15
Dockerfile
15
Dockerfile
|
@ -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"]
|
|
21
LICENSE
21
LICENSE
|
@ -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.
|
|
15
Pipfile
15
Pipfile
|
@ -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"
|
|
||||||
|
|
|
@ -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": {}
|
|
||||||
}
|
|
313
blockchain.py
313
blockchain.py
|
@ -1,301 +1,66 @@
|
||||||
import hashlib
|
|
||||||
import json
|
import json
|
||||||
from time import time
|
import os
|
||||||
from urllib.parse import urlparse
|
|
||||||
from uuid import uuid4
|
|
||||||
|
|
||||||
import requests
|
from datetime import datetime
|
||||||
from flask import Flask, jsonify, request
|
from hashlib import sha256, md5
|
||||||
|
|
||||||
|
|
||||||
class Blockchain:
|
class Blockchain(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.current_transactions = []
|
|
||||||
self.chain = []
|
self.chain = []
|
||||||
self.nodes = set()
|
self.pending_transactions = []
|
||||||
|
|
||||||
# Create the genesis block
|
# Create the genesis block
|
||||||
self.new_block(previous_hash='1', proof=100)
|
print("Creating genesis block")
|
||||||
|
|
||||||
def register_node(self, address):
|
self.new_block()
|
||||||
"""
|
|
||||||
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
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
def new_block(self, previous_hash=None, nonce=None):
|
||||||
block = {
|
block = {
|
||||||
'index': len(self.chain) + 1,
|
'index': len(self.chain),
|
||||||
'timestamp': time(),
|
'timestamp': datetime.utcnow().isoformat(),
|
||||||
'transactions': self.current_transactions,
|
'transactions': self.pending_transactions,
|
||||||
'proof': proof,
|
'previous_hash': previous_hash,
|
||||||
'previous_hash': previous_hash or self.hash(self.chain[-1]),
|
'nonce': nonce,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Reset the current list of transactions
|
# Get the hash of this new block, and add it to the block
|
||||||
self.current_transactions = []
|
block["hash"] = self.hash(block)
|
||||||
|
|
||||||
|
print(f"Created block {block['index']}")
|
||||||
|
|
||||||
|
# Add the block to the chain
|
||||||
self.chain.append(block)
|
self.chain.append(block)
|
||||||
|
|
||||||
|
# Reset the list of pending transactions
|
||||||
|
self.pending_transactions = []
|
||||||
return block
|
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
|
@staticmethod
|
||||||
def hash(block):
|
def hash(block):
|
||||||
"""
|
# Transform the block dict into a json string with sorted keys
|
||||||
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
|
|
||||||
block_string = json.dumps(block, sort_keys=True).encode()
|
block_string = json.dumps(block, sort_keys=True).encode()
|
||||||
return hashlib.sha256(block_string).hexdigest()
|
|
||||||
|
|
||||||
def proof_of_work(self, last_block):
|
# ... and hash it
|
||||||
"""
|
return sha256(block_string).hexdigest()
|
||||||
Simple Proof of Work Algorithm:
|
|
||||||
|
|
||||||
- Find a number p' such that hash(pp') contains leading 4 zeroes
|
def last_block(self):
|
||||||
- Where p is the previous proof, and p' is the new proof
|
# Returns the last block in the chain (if there are blocks)
|
||||||
|
return self.chain[-1] if self.chain else None
|
||||||
:param last_block: <dict> last Block
|
|
||||||
:return: <int>
|
|
||||||
"""
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def valid_proof(last_proof, proof, last_hash):
|
def pow_is_acceptable(hash_of_block, difficulty):
|
||||||
"""
|
return hash_of_block[:difficulty] == "0" * difficulty
|
||||||
Validates the Proof
|
|
||||||
|
|
||||||
:param last_proof: <int> Previous Proof
|
@staticmethod
|
||||||
:param proof: <int> Current Proof
|
def nonce():
|
||||||
:param last_hash: <str> The hash of the Previous Block
|
return sha256(os.urandom(16)).hexdigest()
|
||||||
:return: <bool> True if correct, False if not.
|
|
||||||
|
|
||||||
"""
|
def proof_of_work(self, block=None, difficulty=4):
|
||||||
|
block = block or self.last_block()
|
||||||
|
|
||||||
guess = f'{last_proof}{proof}{last_hash}'.encode()
|
while True:
|
||||||
guess_hash = hashlib.sha256(guess).hexdigest()
|
block["nonce"] = self.nonce()
|
||||||
return guess_hash[:4] == "0000"
|
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
|
||||||
# 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)
|
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<configuration>
|
|
||||||
<startup>
|
|
||||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.1"/>
|
|
||||||
</startup>
|
|
||||||
<appSettings>
|
|
||||||
<add key="host" value="localhost" />
|
|
||||||
<add key="port" value="12345" />
|
|
||||||
</appSettings>
|
|
||||||
</configuration>
|
|
|
@ -1,63 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
|
||||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
|
||||||
<PropertyGroup>
|
|
||||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
|
||||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
|
||||||
<ProjectGuid>{D0C795A0-6F20-4A8E-BE44-801678754DA4}</ProjectGuid>
|
|
||||||
<OutputType>Exe</OutputType>
|
|
||||||
<RootNamespace>BlockChainDemo.Console</RootNamespace>
|
|
||||||
<AssemblyName>BlockChainDemo.Console</AssemblyName>
|
|
||||||
<TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
|
|
||||||
<FileAlignment>512</FileAlignment>
|
|
||||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
|
||||||
<SccProjectName>SAK</SccProjectName>
|
|
||||||
<SccLocalPath>SAK</SccLocalPath>
|
|
||||||
<SccAuxPath>SAK</SccAuxPath>
|
|
||||||
<SccProvider>SAK</SccProvider>
|
|
||||||
<TargetFrameworkProfile />
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
|
||||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
|
||||||
<DebugSymbols>true</DebugSymbols>
|
|
||||||
<DebugType>full</DebugType>
|
|
||||||
<Optimize>false</Optimize>
|
|
||||||
<OutputPath>bin\Debug\</OutputPath>
|
|
||||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
|
||||||
<ErrorReport>prompt</ErrorReport>
|
|
||||||
<WarningLevel>4</WarningLevel>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
|
||||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
|
||||||
<DebugType>pdbonly</DebugType>
|
|
||||||
<Optimize>true</Optimize>
|
|
||||||
<OutputPath>bin\Release\</OutputPath>
|
|
||||||
<DefineConstants>TRACE</DefineConstants>
|
|
||||||
<ErrorReport>prompt</ErrorReport>
|
|
||||||
<WarningLevel>4</WarningLevel>
|
|
||||||
</PropertyGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<Reference Include="System" />
|
|
||||||
<Reference Include="System.Core" />
|
|
||||||
<Reference Include="System.Xml.Linq" />
|
|
||||||
<Reference Include="System.Data.DataSetExtensions" />
|
|
||||||
<Reference Include="Microsoft.CSharp" />
|
|
||||||
<Reference Include="System.Data" />
|
|
||||||
<Reference Include="System.Net.Http" />
|
|
||||||
<Reference Include="System.Xml" />
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<Compile Include="Program.cs" />
|
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<None Include="App.config" />
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\BlockChain\BlockChain.csproj">
|
|
||||||
<Project>{e06fc4ce-77d0-4a64-94a6-32a08920e481}</Project>
|
|
||||||
<Name>BlockChain</Name>
|
|
||||||
</ProjectReference>
|
|
||||||
</ItemGroup>
|
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
|
||||||
</Project>
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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")]
|
|
|
@ -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
|
|
|
@ -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<Transaction> 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}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<Transaction> _currentTransactions = new List<Transaction>();
|
|
||||||
private List<Block> _chain = new List<Block>();
|
|
||||||
private List<Node> _nodes = new List<Node>();
|
|
||||||
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<Block> 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<Block> 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<Block>(),
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,73 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
|
||||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
|
||||||
<PropertyGroup>
|
|
||||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
|
||||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
|
||||||
<ProjectGuid>{E06FC4CE-77D0-4A64-94A6-32A08920E481}</ProjectGuid>
|
|
||||||
<OutputType>Library</OutputType>
|
|
||||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
|
||||||
<RootNamespace>BlockChainDemo</RootNamespace>
|
|
||||||
<AssemblyName>BlockChainDemo</AssemblyName>
|
|
||||||
<TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
|
|
||||||
<FileAlignment>512</FileAlignment>
|
|
||||||
<SccProjectName>SAK</SccProjectName>
|
|
||||||
<SccLocalPath>SAK</SccLocalPath>
|
|
||||||
<SccAuxPath>SAK</SccAuxPath>
|
|
||||||
<SccProvider>SAK</SccProvider>
|
|
||||||
<TargetFrameworkProfile />
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
|
||||||
<DebugSymbols>true</DebugSymbols>
|
|
||||||
<DebugType>full</DebugType>
|
|
||||||
<Optimize>false</Optimize>
|
|
||||||
<OutputPath>bin\Debug\</OutputPath>
|
|
||||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
|
||||||
<ErrorReport>prompt</ErrorReport>
|
|
||||||
<WarningLevel>4</WarningLevel>
|
|
||||||
</PropertyGroup>
|
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
|
||||||
<DebugType>pdbonly</DebugType>
|
|
||||||
<Optimize>true</Optimize>
|
|
||||||
<OutputPath>bin\Release\</OutputPath>
|
|
||||||
<DefineConstants>TRACE</DefineConstants>
|
|
||||||
<ErrorReport>prompt</ErrorReport>
|
|
||||||
<WarningLevel>4</WarningLevel>
|
|
||||||
</PropertyGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<Reference Include="Newtonsoft.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
|
||||||
<HintPath>..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="System" />
|
|
||||||
<Reference Include="System.configuration" />
|
|
||||||
<Reference Include="System.Core" />
|
|
||||||
<Reference Include="System.Xml.Linq" />
|
|
||||||
<Reference Include="System.Data.DataSetExtensions" />
|
|
||||||
<Reference Include="Microsoft.CSharp" />
|
|
||||||
<Reference Include="System.Data" />
|
|
||||||
<Reference Include="System.Net.Http" />
|
|
||||||
<Reference Include="System.Xml" />
|
|
||||||
<Reference Include="TinyWebServer, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
|
||||||
<HintPath>..\packages\TinyWebServer.dll.1.0.1\lib\net40\TinyWebServer.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<Compile Include="Block.cs" />
|
|
||||||
<Compile Include="BlockChain.cs" />
|
|
||||||
<Compile Include="Node.cs" />
|
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
|
||||||
<Compile Include="Transaction.cs" />
|
|
||||||
<Compile Include="WebServer.cs" />
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<None Include="packages.config" />
|
|
||||||
</ItemGroup>
|
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
|
||||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
|
||||||
Other similar extension points exist, see Microsoft.Common.targets.
|
|
||||||
<Target Name="BeforeBuild">
|
|
||||||
</Target>
|
|
||||||
<Target Name="AfterBuild">
|
|
||||||
</Target>
|
|
||||||
-->
|
|
||||||
</Project>
|
|
|
@ -1,9 +0,0 @@
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace BlockChainDemo
|
|
||||||
{
|
|
||||||
public class Node
|
|
||||||
{
|
|
||||||
public Uri Address { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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")]
|
|
|
@ -1,9 +0,0 @@
|
||||||
namespace BlockChainDemo
|
|
||||||
{
|
|
||||||
public class Transaction
|
|
||||||
{
|
|
||||||
public int Amount { get; set; }
|
|
||||||
public string Recipient { get; set; }
|
|
||||||
public string Sender { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<Transaction>(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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<packages>
|
|
||||||
<package id="Newtonsoft.Json" version="10.0.3" targetFramework="net451" />
|
|
||||||
<package id="TinyWebServer.dll" version="1.0.1" targetFramework="net451" />
|
|
||||||
</packages>
|
|
|
@ -1,2 +1,3 @@
|
||||||
flask==0.12.2
|
flask==1.0.2
|
||||||
requests==2.18.4
|
requests==2.18.4
|
||||||
|
pytest==4.4.0
|
||||||
|
|
|
@ -1,104 +1,51 @@
|
||||||
import hashlib
|
import pytest
|
||||||
import json
|
|
||||||
from unittest import TestCase
|
|
||||||
|
|
||||||
from blockchain import Blockchain
|
from blockchain import Blockchain
|
||||||
|
|
||||||
|
|
||||||
class BlockchainTestCase(TestCase):
|
@pytest.fixture
|
||||||
|
def blockchain():
|
||||||
def setUp(self):
|
return Blockchain()
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TestRegisterNodes(BlockchainTestCase):
|
class TestBlockchain:
|
||||||
|
def test_genesis_block(self, blockchain):
|
||||||
|
genesis_block = blockchain.chain[0]
|
||||||
|
|
||||||
def test_valid_nodes(self):
|
assert len(blockchain.chain) == 1
|
||||||
blockchain = Blockchain()
|
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):
|
def test_blocks_are_hashed_correctly(self, blockchain):
|
||||||
blockchain = 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):
|
assert blockchain.last_block() == some_block
|
||||||
blockchain = Blockchain()
|
|
||||||
|
|
||||||
blockchain.register_node('http://192.168.0.1:5000')
|
def test_pow_is_acceptable(self, blockchain):
|
||||||
blockchain.register_node('http://192.168.0.1:5000')
|
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):
|
assert mined_block["nonce"]
|
||||||
|
assert blockchain.hash(mined_block)[:3] == "000"
|
||||||
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)
|
|
||||||
|
|
Loading…
Reference in New Issue