just updating

This commit is contained in:
Daniel van Flymen 2017-12-25 19:12:25 +02:00
parent 4010cf3273
commit 0df3b84428
21 changed files with 484 additions and 811 deletions

10
Pipfile
View File

@ -1,15 +1,23 @@
[[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"
sqlalchemy = "*"
aioodbc = "*"
sqlalchemy-aio = "*"

83
Pipfile.lock generated
View File

@ -1,20 +1,20 @@
{
"_meta": {
"hash": {
"sha256": "45af71184b5b013f58a8601f7f87c9f0b882afe19f197ce45d6d08e46d615159"
"sha256": "d2951f633be558eed649ff12d93c9acbeb9ada5f228abff3e5a873891a6a78dc"
},
"host-environment-markers": {
"implementation_name": "cpython",
"implementation_version": "3.6.2",
"implementation_version": "3.6.3",
"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",
"platform_release": "17.0.0",
"platform_system": "Darwin",
"platform_version": "Darwin Kernel Version 17.0.0: Thu Aug 24 21:48:19 PDT 2017; root:xnu-4570.1.46~2/RELEASE_X86_64",
"python_full_version": "3.6.3",
"python_version": "3.6",
"sys_platform": "linux"
"sys_platform": "darwin"
},
"pipfile-spec": 6,
"requires": {
@ -29,12 +29,18 @@
]
},
"default": {
"aioodbc": {
"hashes": [
"sha256:1a859a4ac7de85bb7a743e22da3942fb046c18fed27fb68bb2ac01750ed259a7"
],
"version": "==0.2.0"
},
"certifi": {
"hashes": [
"sha256:54a07c09c586b0e4c619f02a5e94e36619da8e2b053e20f594348c0611803704",
"sha256:40523d2efb60523e113b44602298f0960e900388cf3bb6043f645cf57ea9e3f5"
"sha256:244be0d93b71e93fc0a0a479862051414d0e00e16435707e5bf5000f92e04694",
"sha256:5ec74291ca1136b40f0379e1128ff80e866597e4e2c1e755739a913bbc3613c0"
],
"version": "==2017.7.27.1"
"version": "==2017.11.5"
},
"chardet": {
"hashes": [
@ -72,10 +78,10 @@
},
"jinja2": {
"hashes": [
"sha256:2231bace0dfd8d2bf1e5d7e41239c06c9e0ded46e70cc1094a0aa64b0afeb054",
"sha256:ddaa01a212cd6d641401cb01b605f4a4d9f37bfc93043d7f760ec70fb99ff9ff"
"sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd",
"sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4"
],
"version": "==2.9.6"
"version": "==2.10"
},
"markupsafe": {
"hashes": [
@ -83,6 +89,31 @@
],
"version": "==1.0"
},
"pyodbc": {
"hashes": [
"sha256:364621505a7488b58ac3d5ac53183425f0ba6f333fdc0500492d08485431d04c",
"sha256:f20474fcf5f359d6079e215b196af3a3667c73712437f20cb89d72819ac7d37d",
"sha256:ec84f3c7b13199455a42ae130815855be7a4c18ac4cf01f005785890cfcf52e5",
"sha256:c0f1e7b814cbdaa0215ff72a17e3cb3f4da77c65806b3895bb40f3859822a57c",
"sha256:75c31fcfa8f5f1d09485817488de60d6ebe8819593c83beacf625dd281fbe406",
"sha256:74cd2dff259eaf3da79c5beed2818f2cb1c0d15212f3b6a02e08053f5de0e7fc",
"sha256:3f06a002c38ad241e072bc6449c9961afcb37bbbb9feeda20f90a9d67b02fe14",
"sha256:7139faaa8f1396883f275e7ada88e729b031f9efae335d0a9ea26e84e2eb11eb",
"sha256:1242b5f045249835b8473565882804bb09aa54cc1c5bad24ef6cd8b7af4197d0",
"sha256:d40b122108ae48d17531c9ba844168c1c82d76247ef22063d3f087e30fa1dde2",
"sha256:3f119da1db79576ce15d36b8c31dc7ca1732ada47a6bfd0de0993c68440b26a0",
"sha256:ecf9a63cd5babce5547ac1b4b8472d6dfec42ca009cd3d8c8b2777b2b4a2e2a8",
"sha256:9655f84ca9e5cb2dfffff705601017420c840d55271ba62dd44f05383eff0329"
],
"version": "==4.0.21"
},
"represent": {
"hashes": [
"sha256:f599094a66ec04455bb1555f3a65c78449f647a1a1bcd513775cfa64c8067400",
"sha256:9c12c1c60b8616f97855d3a37ea4ec60e3d5d463965a6a29b80d0b794ad4f0d8"
],
"version": "==1.5.1"
},
"requests": {
"hashes": [
"sha256:6a1b267aa90cac58ac3a765d067950e7dbbf75b1da07e895d1f594193a40a38b",
@ -90,6 +121,26 @@
],
"version": "==2.18.4"
},
"six": {
"hashes": [
"sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb",
"sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9"
],
"version": "==1.11.0"
},
"sqlalchemy": {
"hashes": [
"sha256:8b79a5ed91cdcb5abe97b0045664c55c140aec09e5dd5c01303e23de5fe7a95a"
],
"version": "==1.1.15"
},
"sqlalchemy-aio": {
"hashes": [
"sha256:2c2a46504c999b4ae5751c90a86aa78cee8cc216f9d432264fb850ffe2d78965",
"sha256:736ab30fed217bc9602824b8b7daafcd52e0e19eb4cf07a940d82de5981877e8"
],
"version": "==0.11.0"
},
"urllib3": {
"hashes": [
"sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b",
@ -99,10 +150,10 @@
},
"werkzeug": {
"hashes": [
"sha256:e8549c143af3ce6559699a01e26fa4174f4c591dbee0a499f3cd4c3781cdec3d",
"sha256:903a7b87b74635244548b30d30db4c8947fe64c5198f58899ddcd3a13c23bb26"
"sha256:f3000aa146ce8a9da8ca3e978e0e931c2a58eb56c323a5efb6b4307f7832b549",
"sha256:6246e5fc98a505824113fb6aca993d45ea284a2bcffdc2c65d0c538e53e4abd3"
],
"version": "==0.12.2"
"version": "==0.13"
}
},
"develop": {}

View File

@ -1,35 +1,31 @@
import hashlib
import json
from time import time
from urllib.parse import urlparse
from uuid import uuid4
from datetime import datetime
import requests
from flask import Flask, jsonify, request
from database import Block, db
class Blockchain:
def __init__(self):
self.current_transactions = []
self.chain = []
self.nodes = set()
self.difficulty = 4
# Create the genesis block
self.new_block(previous_hash='1', proof=100)
# Create the genesis block with a height of 0
self.new_block(previous_hash='1', proof=100, height=0)
def register_node(self, address):
def get_blocks(self, height=0):
"""
Add a new node to the list of nodes
Returns all blocks from a given height
:param address: Address of node. Eg. 'http://192.168.0.5:5000'
:param height: <int> The height from which to return blocks
:return:
"""
parsed_url = urlparse(address)
self.nodes.add(parsed_url.netloc)
blocks = db.query(Block).filter(Block.height >= height).all()
return [block.to_dict() for block in blocks]
def valid_chain(self, chain):
"""
Determine if a given blockchain is valid
Determines if a given blockchain is valid
:param chain: A blockchain
:return: True if valid, False if not
@ -40,9 +36,7 @@ class Blockchain:
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
if block['previous_hash'] != self.hash(last_block):
return False
@ -56,61 +50,30 @@ class Blockchain:
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):
def new_block(self, proof, previous_hash, height):
"""
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
:param height: The Height of the new block
:return: New Block
"""
block = {
'index': len(self.chain) + 1,
'timestamp': time(),
'transactions': self.current_transactions,
'proof': proof,
'previous_hash': previous_hash or self.hash(self.chain[-1]),
}
block = Block(
height=height,
timestamp=datetime.now(),
transactions=self.current_transactions,
proof=proof,
previous_hash=previous_hash,
)
db.add(block)
db.commit()
# Reset the current list of transactions
self.current_transactions = []
self.chain.append(block)
return block
def new_transaction(self, sender, recipient, amount):
@ -132,7 +95,12 @@ class Blockchain:
@property
def last_block(self):
return self.chain[-1]
"""
Returns the last block in the Blockchain (greatest height)
:return: <Block>
"""
return db.query(Block).order_by(Block.height.desc()).first()
@staticmethod
def hash(block):
@ -142,8 +110,7 @@ class Blockchain:
: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 = block.to_json().encode()
return hashlib.sha256(block_string).hexdigest()
def proof_of_work(self, last_proof):
@ -151,6 +118,8 @@ class Blockchain:
Simple Proof of Work Algorithm:
- Find a number p' such that hash(pp') contains leading 4 zeroes, where p is the previous p'
- p is the previous proof, and p' is the new proof
:param last_proof: The proof from the previous block
"""
proof = 0
@ -159,8 +128,7 @@ class Blockchain:
return proof
@staticmethod
def valid_proof(last_proof, proof):
def valid_proof(self, last_proof, proof):
"""
Validates the Proof
@ -171,115 +139,96 @@ class Blockchain:
guess = f'{last_proof}{proof}'.encode()
guess_hash = hashlib.sha256(guess).hexdigest()
return guess_hash[:4] == "0000"
return guess_hash[:self.difficulty] == '0' * self.difficulty # In Python, '0' * 4 gives '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
# last_proof = last_block['proof']
# proof = blockchain.proof_of_work(last_proof)
#
# # 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('/mine', methods=['GET'])
def mine():
# We run the proof of work algorithm to get the next proof...
last_block = blockchain.last_block
last_proof = last_block['proof']
proof = blockchain.proof_of_work(last_proof)
# 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)
# @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
#

View File

@ -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>

View File

@ -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>

View File

@ -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();
}
}
}

View File

@ -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")]

View File

@ -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

View File

@ -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}";
}
}
}

View File

@ -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;
}
}
}

View File

@ -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>

View File

@ -1,9 +0,0 @@
using System;
namespace BlockChainDemo
{
public class Node
{
public Uri Address { get; set; }
}
}

View File

@ -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")]

View File

@ -1,9 +0,0 @@
namespace BlockChainDemo
{
public class Transaction
{
public int Amount { get; set; }
public string Recipient { get; set; }
public string Sender { get; set; }
}
}

View File

@ -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();
}
}
}

View File

@ -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>

64
database.py Normal file
View File

@ -0,0 +1,64 @@
import json
from sqlalchemy import Column, DateTime, Integer, PickleType, String, create_engine
from sqlalchemy.ext.declarative import declarative_base, declared_attr
from sqlalchemy.orm import scoped_session, sessionmaker
# Set up the Database
engine = create_engine('sqlite:///electron.db')
db = scoped_session(sessionmaker(bind=engine))
class BaseModel(object):
@declared_attr
def __tablename__(self):
return self.__name__.lower() # Ensures all tables have the same name as their models (below)
def to_json(self):
"""
Convenience method to convert any database row to JSON
:return: <JSON>
"""
return json.dumps({c.name: getattr(self, c.name) for c in self.__table__.columns}, sort_keys=True)
def to_dict(self):
"""
Convenience method to convert any database row to dict
:return: <JSON>
"""
return {c.name: getattr(self, c.name) for c in self.__table__.columns}
Base = declarative_base(cls=BaseModel)
class Peer(Base):
identifier = Column(String(32), primary_key=True)
ip = Column(String, index=True, unique=True)
class Block(Base):
height = Column(Integer, primary_key=True, autoincrement=True)
timestamp = Column(DateTime, index=True)
transactions = Column(PickleType)
previous_hash = Column(String(64))
proof = Column(String(64))
hash = Column(String(64))
class Config(Base):
key = Column(String(64), primary_key=True, unique=True)
value = Column(PickleType)
def reset_db():
"""
Deletes and Re-creates the Database
:return:
"""
Base.metadata.drop_all(bind=engine)
Base.metadata.create_all(bind=engine)

23
helpers.py Normal file
View File

@ -0,0 +1,23 @@
from database import db, Config
def set_config(key, value, replace=False):
config_value = get_config(key)
if config_value is None:
db.add(Config(key=key, value=value))
db.commit()
return
if config_value != value and replace is True:
db.add(Config(key=key, value=value))
db.commit()
return
def get_config(key, default=None):
config = db.query(Config).filter_by(key=key).first()
if config:
return config.value
else:
return default

42
networking.py Normal file
View File

@ -0,0 +1,42 @@
import miniupnpc
upnpc = miniupnpc.UPnP()
upnpc.discoverdelay = 200
ndevices = upnpc.discover()
print('%d UPNP device(s) detected', ndevices)
upnpc.selectigd()
external_ip = upnpc.externalipaddress()
print('external ip: %s', external_ip)
print('status: %s, connection type: %s',
upnpc.statusinfo(),
upnpc.connectiontype())
# find a free port for the redirection
port = 8080
external_port = port
found = False
while True:
redirect = upnpc.getspecificportmapping(external_port, 'TCP')
if redirect is None:
found = True
break
if external_port >= 65535:
break
external_port = external_port + 1
print('No redirect candidate %s TCP => %s port %u TCP',
external_ip, upnpc.lanaddr, port)
print('trying to redirect %s port %u TCP => %s port %u TCP',
external_ip, external_port, upnpc.lanaddr, port)
res = upnpc.addportmapping(external_port, 'TCP',
upnpc.lanaddr, port,
'pyethereum p2p port %u' % external_port,
'')
print('Success to redirect %s port %u TCP => %s port %u TCP',
external_ip, external_port, upnpc.lanaddr, port)

50
node.py Normal file
View File

@ -0,0 +1,50 @@
from uuid import uuid4
from sanic import Sanic
from sanic.response import json
from sqlalchemy import func
from blockchain import Blockchain
from database import Peer, db, reset_db
from helpers import get_config, set_config
from tasks import populate_peers, watch_blockchain, add_stuff, mining_controller
app = Sanic()
app.debug = True
@app.listener('before_server_start')
async def set_node_identifier(_app, loop):
node_identifier = get_config('node_identifier')
if not node_identifier:
set_config(key='node_identifier', value=uuid4().hex)
reset_db()
app.blockchain = Blockchain()
app.add_task(populate_peers(app))
# app.add_task(watch_blockchain(app))
# app.add_task(add_stuff(app))
app.add_task(mining_controller)
@app.route("/")
async def peers(request):
random_peers = db.query(Peer).order_by(func.random()).limit(256).all()
return json(random_peers)
@app.route("/transactions")
async def current_transactions(request):
return json(app.blockchain.current_transactions())
@app.route("/blocks")
async def blocks(request):
# height = request.parsed_args['height']
blocks = app.blockchain.get_blocks()
return json(blocks)
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8080)

105
tasks.py Normal file
View File

@ -0,0 +1,105 @@
import time
import asyncio
import multiprocessing
import aiohttp
from sqlalchemy import func
from database import Peer, db
def get_random_peers(limit=10):
"""
Returns random peers
:param limit: How many peers to return
:return:
"""
return db.query(Peer).order_by(func.random()).limit(limit)
async def populate_peers(app):
"""
Ask random peers to return peers they know about
"""
print(app)
while True:
peers = get_random_peers()
async with aiohttp.ClientSession() as session:
for peer in peers:
try:
async with session.get(f'http://{peer.ip}:8000', timeout=3) as resp:
print(resp.status)
print(await resp.text())
except asyncio.TimeoutError:
db.delete(peer)
db.commit()
print(f"{peer.ip}: Deleting node")
await asyncio.sleep(10)
async def watch_blockchain(app):
while True:
print(f"TXN: {app.blockchain.current_transactions}")
await asyncio.sleep(2)
async def add_stuff(app):
while True:
await asyncio.sleep(1.5)
app.blockchain.current_transactions.append("a")
async def consensus():
"""
Our Consensus Algorithm. It makes sure we have a valid up-to-date chain.
"""
# Asynchronously grab the chain from each peer
# Validate it, then replace ours if necessary
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 miner():
while True:
time.sleep(2)
print('Hey! Mining is happening!')
async def mining_controller():
p = multiprocessing.Process(target=miner, args=())
p.start()