Compare commits
1 Commits
master
...
dvf/long-f
Author | SHA1 | Date |
---|---|---|
Daniel van Flymen | 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
|
||||
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: <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
|
||||
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: <int> Previous Proof
|
||||
:param proof: <int> Current Proof
|
||||
:param last_hash: <str> The hash of the Previous Block
|
||||
:return: <bool> 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
|
||||
|
|
|
@ -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
|
||||
pytest==4.4.0
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue