Compare commits

...

10 Commits

Author SHA1 Message Date
Daniel van Flymen d38a86b25f updates 2017-12-29 16:37:10 -05:00
Daniel van Flymen 8cd2a13747 more cleanup 2017-12-29 15:38:57 -05:00
Daniel van Flymen acb84b81e0 more cleanup 2017-12-29 15:20:52 -05:00
Daniel van Flymen 6f34e4fd7b clean up 2017-12-29 15:14:09 -05:00
Daniel van Flymen d3298af019 rather get hashes in a better way 2017-12-29 14:32:55 -05:00
Daniel van Flymen fd7cf5e2f9 updates 2017-12-29 13:52:43 -05:00
Joel Bixby bfe4de5016 Minor fixups while working together 2017-12-28 16:59:48 -05:00
Daniel van Flymen ecef87c5de Remove unnecessary methods 2017-12-28 15:54:57 -05:00
Daniel van Flymen 5021b69937 Updating for collaborators 2017-12-28 15:52:11 -05:00
Daniel van Flymen 0df3b84428 just updating 2017-12-25 19:12:25 +02:00
24 changed files with 630 additions and 854 deletions

2
.gitignore vendored
View File

@ -102,3 +102,5 @@ ENV/
# PyCharm
.idea/
*.db

13
Pipfile
View File

@ -1,15 +1,26 @@
[[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 = "*"
sanic = "*"
aiohttp = "*"
miniupnpc = "*"

225
Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "45af71184b5b013f58a8601f7f87c9f0b882afe19f197ce45d6d08e46d615159"
"hash_block": {
"sha256": "12bb22c1e036c3ea23ae6dfcd3682d0ff1f37b0e7817ba87aa3fe22ceb12ed9d"
},
"host-environment-markers": {
"implementation_name": "cpython",
@ -9,12 +9,12 @@
"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",
"platform_release": "16.7.0",
"platform_system": "Darwin",
"platform_version": "Darwin Kernel Version 16.7.0: Thu Jun 15 17:36:27 PDT 2017; root:xnu-3789.70.16~2/RELEASE_X86_64",
"python_full_version": "3.6.2",
"python_version": "3.6",
"sys_platform": "linux"
"sys_platform": "darwin"
},
"pipfile-spec": 6,
"requires": {
@ -29,12 +29,59 @@
]
},
"default": {
"aiofiles": {
"hashes": [
"sha256:25c66ea3872d05d53292a6b3f7fa0f86691512076446d83a505d227b5e76f668",
"sha256:852a493a877b73e11823bfd4e8e5ef2610d70d12c9eaed961bcd9124d8de8c10"
],
"version": "==0.3.2"
},
"aiohttp": {
"hashes": [
"sha256:1d3659809cc3cf16007a43df3c3af34a9ad8d7594bfcd651ef2d29ff21d015e3",
"sha256:18c93827f604e3830535423f22bfaa180d7ba10baa5959a2077f2e29b320138d",
"sha256:080c82112d93fe117a2f605d5a102191ae7fc52349c53cf6676efbfb8bd2d369",
"sha256:a1c29fdc56e040c3c67a9fa6da7e05382d5216d1ead9ae8a4fb772a1abb0452a",
"sha256:d8f546159ae453572c3b87d88652705c4516dfee1ade8673b47f544b2bf1b33d",
"sha256:03085220b503bb2cf2d288e1b36cf6dcbf84fcfed550e7c73bad429a6e528084",
"sha256:00e40b1261bdb6a1e2b986e610be8a2bb0699ce5a261f78c88d761c726e0af10",
"sha256:fcec0a4878c27f04bf62de4b76d51f9583d45031317dd020088d2e258210fcc2",
"sha256:52b180767e1b75ff071f316a52946fb311ba4183cb6981201fa7843611cf42e4",
"sha256:1fbc4701639ca383dc103840fb478ce726b84c51de8d575c02b740bcb2f60262",
"sha256:9d2e10768bca6ff8392df596754adbffee39ab4243d2536f955f9db145685cbe",
"sha256:3ee498748106c2f8ce937ea27c05d8862118ce055ee3d074b383e927572b51b6",
"sha256:dc922785064187c45c71eda21d7eb87c7a0b2d867e0d7c9ffc2ea2a37dcca608",
"sha256:0415ca37ca047d4b5c2938da024abe4893fe54227b7ad36f98fb169fff4767a3",
"sha256:08715cc8d0ae00679b7c131804ae92aacc31fd0078dd0d78c309c043a4f8aa57",
"sha256:ed8fb8c9b16459895c6949215592df6119961a9999ade84b66594456883d2215",
"sha256:6b2c62e6d54a08c7e4b8b00251d3c877bdf10ceec22c7ecc5d94de64d75fe699",
"sha256:f81850cf4707a2d3d85fcb9c85c091a0df66bf4a67197530c5a4f454b8d1d950",
"sha256:5a1c7c890ac13dd05763e3617261f528fedf3255d72ba8c41e97f7de72f3d8b6",
"sha256:65d623d32a40826be88ecafe5a49fd0af3092b2bf7e1171aec1d3e7868c969c1",
"sha256:222634adcdcfda1aefafff198415df77946384d10696619f1b163cb36d03bc82",
"sha256:fe294df38e9c67374263d783a7a29c79372030f5962bd5734fa51c6f4bbfee3b"
],
"version": "==2.3.7"
},
"aioodbc": {
"hashes": [
"sha256:1a859a4ac7de85bb7a743e22da3942fb046c18fed27fb68bb2ac01750ed259a7"
],
"version": "==0.2.0"
},
"async-timeout": {
"hashes": [
"sha256:d3a195a827b0f4068d1616ae2da04aac62e365d14f2b13dbc071f9feed9db4e2",
"sha256:c17d8ac2d735d59aa62737d76f2787a6c938f5a944ecf768a8c0ab70b0dea566"
],
"version": "==2.0.0"
},
"certifi": {
"hashes": [
"sha256:54a07c09c586b0e4c619f02a5e94e36619da8e2b053e20f594348c0611803704",
"sha256:40523d2efb60523e113b44602298f0960e900388cf3bb6043f645cf57ea9e3f5"
"sha256:244be0d93b71e93fc0a0a479862051414d0e00e16435707e5bf5000f92e04694",
"sha256:5ec74291ca1136b40f0379e1128ff80e866597e4e2c1e755739a913bbc3613c0"
],
"version": "==2017.7.27.1"
"version": "==2017.11.5"
},
"chardet": {
"hashes": [
@ -57,6 +104,12 @@
],
"version": "==0.12.2"
},
"httptools": {
"hashes": [
"sha256:f50dcb27178416c3a4113e9e1b392be5d1ff56ae1e474fe80869ed8530505e4c"
],
"version": "==0.0.10"
},
"idna": {
"hashes": [
"sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4",
@ -72,10 +125,10 @@
},
"jinja2": {
"hashes": [
"sha256:2231bace0dfd8d2bf1e5d7e41239c06c9e0ded46e70cc1094a0aa64b0afeb054",
"sha256:ddaa01a212cd6d641401cb01b605f4a4d9f37bfc93043d7f760ec70fb99ff9ff"
"sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd",
"sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4"
],
"version": "==2.9.6"
"version": "==2.10"
},
"markupsafe": {
"hashes": [
@ -83,6 +136,64 @@
],
"version": "==1.0"
},
"miniupnpc": {
"hashes": [
"sha256:7ea46c93486fe1bdb31f0e0c2d911d224fce70bf5ea120e4295d647dfe274931"
],
"version": "==2.0.2"
},
"multidict": {
"hashes": [
"sha256:d12dfcff45b5c0eb3d586289cbf928012e75f93f10f4b9d7af903acb07b3c226",
"sha256:f7deb65a184cbe757faaf4c6d2b8f203cb1a11dd44603a34d7befc343253d5bf",
"sha256:80ad69b8330135b52a6da7a1f0ab6a278025bb72ea08768966785c829f515a6f",
"sha256:3261b631cd6d079e6a20def3306b433794f800ea896dce4f1d5e833ad9bf6f26",
"sha256:80e2bd17ea98fe77867771e1ee03433a54fa492a8413966a6caa766bdd6d6e62",
"sha256:8eb59892040198741944eafca0c34be4da6b58be7fddf6e491a2d9e7e1767548",
"sha256:24ec0d645ca70981e1c84e97e48c874b47f4970a173c8c751be9c213a8658e40",
"sha256:167558cfb7c43077b3d8602142109f2b52fae0841a7ff97eb0a7443db199f303",
"sha256:80abc93d197e24fb7c5aa5a8be3eacbcbc7115a2ff64e46162a4c2b46cd2f75a",
"sha256:ff2b92a41df2e4c5cafaefb782469f3e6053ed00026d75a398521096192d0408",
"sha256:5cfb63f0ddfa86ce6e80eee64e4fe886f9dfdb6dbbd724997bccb66513b0e29f",
"sha256:50dac1151e34974b04419073ee9726f4180c6daf3454fd4af258824a19ad8c1d",
"sha256:ebf1ba2af62dbaa37cf3db346830bb43d40d05a5f1f1966888a620f9b795e7c7",
"sha256:3f513f3bf933d7cb6f5741f6676d4fac1e96aa634161c071974f9bb86a7bbac9",
"sha256:ef6dc6e2d51b6058aa62cdd44dbf02c250c19eee6ff0065babc0ac126b068d43",
"sha256:90dc3b8fefc58a865d64957f8f901d724250ba2a40e02f49d0df0103e96f5afa",
"sha256:70630854b820d73ae102440123df38c983d77cd4ae444f3930a6bb6bbda87b76",
"sha256:faf2b6447521d2075d03fb5e7c5467bac68f67df1c69e034ebca3afb6b3c619d",
"sha256:eb7d5d39463137726138fc14c8458131136b8f4b06ba65f1cecd7aa6abe91df1",
"sha256:fa04df1503fae7045883c57db47ba0c07de2ebe4a91c3e64d56f20d3d99e5dc8",
"sha256:bce16633f3ee88863e4c9bcd7e037a6133c56fd9e7e7c0776bbaeeddcf154ac4",
"sha256:f82e61c7408ed0dce1862100db55595481911f159d6ddec0b375d35b6449509b"
],
"version": "==3.3.2"
},
"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 +201,39 @@
],
"version": "==2.18.4"
},
"sanic": {
"hashes": [
"sha256:18a3bd729093ac93a245849c44045c505a11e6d36da5bf231cb986bfb1e3c14c",
"sha256:22b1a6f1dc55db8a136335cb0961afa95040ca78aa8c78425a40d91e8618e60e"
],
"version": "==0.7.0"
},
"six": {
"hashes": [
"sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb",
"sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9"
],
"version": "==1.11.0"
},
"sqlalchemy": {
"hashes": [
"sha256:7dda3e0b1b12215e3bb05368d1abbf7d747112a43738e0a4e6deb466b83fd88e"
],
"version": "==1.2.0"
},
"sqlalchemy-aio": {
"hashes": [
"sha256:2c2a46504c999b4ae5751c90a86aa78cee8cc216f9d432264fb850ffe2d78965",
"sha256:736ab30fed217bc9602824b8b7daafcd52e0e19eb4cf07a940d82de5981877e8"
],
"version": "==0.11.0"
},
"ujson": {
"hashes": [
"sha256:f66073e5506e91d204ab0c614a148d5aa938bdbf104751be66f8ad7a222f5f86"
],
"version": "==1.35"
},
"urllib3": {
"hashes": [
"sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b",
@ -97,12 +241,63 @@
],
"version": "==1.22"
},
"uvloop": {
"hashes": [
"sha256:01cf7199728867f406ba5af78cc47c80acd663ccc52cae105e737a997f1b2bca",
"sha256:e7c871ba3edd5fcf2afb756de88a9a65245070161e24f75abe79c0a241bb8c76",
"sha256:b057ef2b0d0162c1ef257f43a95f59bfec37ee9a75cc5412d6b7f9ac6d1d69cb",
"sha256:89c3bfaad77625490c42a6b99af1879234767ab0c31dd193486a909506e5e549",
"sha256:68574150720a380509a3409bf2941be0199cfdacff144a97502fb29c250ba927",
"sha256:7fba5f390db607b2f026bc598df6b2a2a9e062bffe82910b5ffe2b88560135e5",
"sha256:7ee14835a75c72227d3f8a3f370519a3106a6f02e5453f275f16437ebdb92953"
],
"version": "==0.9.1"
},
"websockets": {
"hashes": [
"sha256:f5192da704535a7cbf76d6e99c1ec4af7e8d1288252bf5a2385d414509ded0cf",
"sha256:0c31bc832d529dc7583d324eb6c836a4f362032a1902723c112cf57883488d8c",
"sha256:da7610a017f5343fdf765f4e0eb6fd0dfd08264ca1565212b110836d9367fc9c",
"sha256:fd81af8cf3e69f9a97f3a6c0623a0527de0f922c2df725f00cd7646d478af632",
"sha256:3d425ae081fb4ba1eef9ecf30472ffd79f8e868297ccc7a47993c96dbf2a819c",
"sha256:ebdd4f18fe7e3bea9bd3bf446b0f4117739478caa2c76e4f0fb72cc45b03cbd7",
"sha256:3859ca16c229ddb0fa21c5090e4efcb037c08ce69b0c1dfed6122c3f98cd0c22",
"sha256:d1a0572b6edb22c9208e3e5381064e09d287d2a915f90233fef994ee7a14a935",
"sha256:80188abdadd23edaaea05ce761dc9a2e1df31a74a0533967f0dcd9560c85add0",
"sha256:fecf51c13195c416c22422353b306dddb9c752e4b80b21e0fa1fccbe38246677",
"sha256:367ff945bc0950ad9634591e2afe50bf2222bc4fad1088a386c4bb700888026e",
"sha256:6df87698022aef2596bffdfecc96d656db59c8d719708c8a471daa815ee61656",
"sha256:341824d8c9ad53fc43cca3fa9407f294125fa258592f7676640396501448e57e",
"sha256:64896a6b3368c959b8096b655e46f03dfa65b96745249f374bd6a35705cc3489",
"sha256:1f3e5a52cab6daa3d432c7b0de0a14109be39d2bfaad033ee5de4a3d3e11dcdf",
"sha256:da4d4fbe059b0453e726d6d993760065d69b823a27efc3040402a6fcfe6a1ed9"
],
"version": "==4.0.1"
},
"werkzeug": {
"hashes": [
"sha256:e8549c143af3ce6559699a01e26fa4174f4c591dbee0a499f3cd4c3781cdec3d",
"sha256:903a7b87b74635244548b30d30db4c8947fe64c5198f58899ddcd3a13c23bb26"
"sha256:f3000aa146ce8a9da8ca3e978e0e931c2a58eb56c323a5efb6b4307f7832b549",
"sha256:6246e5fc98a505824113fb6aca993d45ea284a2bcffdc2c65d0c538e53e4abd3"
],
"version": "==0.12.2"
"version": "==0.13"
},
"yarl": {
"hashes": [
"sha256:55dfe4a952fddf3e32c08e3fe004a86b77963d95a4b36c8bdb5acc68960c4767",
"sha256:5970e4deb2ad23ececbaca8e20e7421d66d84f9a8978eb7d469b931f0a7c5bfa",
"sha256:6594f27c81012c9bcad46693b599ae3cb6086ef0c32af68160e599e69b7ce2e6",
"sha256:6f662ff050945163fc70f6c4361807068f66b248d60d7f7d2fe35c6dce7e1490",
"sha256:3c90b8b89d9e163fb114f8e491f85bc44aacc528d9ade3cb4a210a2340282b15",
"sha256:c1f6aa4f6647380dfee6a2f560079d2c2b105a6d7ab7c1ad83bc811fc0df7bb2",
"sha256:e6c302aa976504e4cf299b5ae5385677b77f84b71f84aeddcc56b8841743b27b",
"sha256:e04c26c4fa6717ceeada129665cf19f630ddffb2df692bc5d51595b97227a5cf",
"sha256:15ab97fd75bd531ba6e3e2639313db177ba44ec3a68bdd3f531904782872dfe9",
"sha256:b3b2bfd039b6a89382ad46a20647286b012db34f63e00e39e185cb4df566121f",
"sha256:620f76a2452f502333392c86074046af6fde72b54065e43530e8417d689b1824",
"sha256:7a2001e180f169f762f9322066685f94808db5722ce7c4827a248d3f50d88e61",
"sha256:47622985ecd9b15335d65c1acd54aeb3ba449e6d09b36e37ecfe334c7e7b8d0b"
],
"version": "==0.16.0"
}
},
"develop": {}

View File

@ -1,35 +1,43 @@
import hashlib
import json
from time import time
from urllib.parse import urlparse
from uuid import uuid4
import logging
from datetime import datetime
import requests
from flask import Flask, jsonify, request
from database import Block, db
from helpers import hash_block
logger = logging.getLogger('root.blockchain')
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 if it doesn't exist
if not self.last_block:
block = self.build_block()
block['hash'] = hash_block(block)
self.save_block(block)
def register_node(self, address):
logger.info("✨ Created genesis block")
logger.info("Blockchain Initiated")
@staticmethod
def get_blocks(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,11 +48,9 @@ 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):
# Check that the hash_block of the block is correct
if block['previous_hash'] != self.hash_block(last_block):
return False
# Check that the Proof of Work is correct
@ -56,63 +62,35 @@ 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 build_block(self):
"""
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
"""
last_block = self.last_block
block = {
'index': len(self.chain) + 1,
'timestamp': time(),
return {
'height': last_block.height + 1 if last_block else 0,
'timestamp': datetime.utcnow(),
'transactions': self.current_transactions,
'proof': proof,
'previous_hash': previous_hash or self.hash(self.chain[-1]),
'previous_hash': last_block.hash if last_block else 0,
'proof': -1,
}
def save_block(self, block_dict):
"""
Saves Block to the database and resets outstanding transactions
:param block_dict: A dictionary representation of a Block
:return:
"""
print(block_dict)
block = Block(**block_dict)
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):
"""
Creates a new transaction to go into the next mined Block
@ -132,154 +110,9 @@ class Blockchain:
@property
def last_block(self):
return self.chain[-1]
@staticmethod
def hash(block):
"""
Creates a SHA-256 hash of a Block
Returns the last block in the Blockchain (greatest height)
:param block: Block
:return: <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()
return hashlib.sha256(block_string).hexdigest()
def proof_of_work(self, last_proof):
"""
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
"""
proof = 0
while self.valid_proof(last_proof, proof) is False:
proof += 1
return proof
@staticmethod
def valid_proof(last_proof, proof):
"""
Validates the Proof
:param last_proof: Previous Proof
:param proof: Current Proof
:return: True if correct, False if not.
"""
guess = f'{last_proof}{proof}'.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
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)
return db.query(Block).order_by(Block.height.desc()).first()

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>

57
database.py Normal file
View File

@ -0,0 +1,57 @@
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))
Base = declarative_base()
class BaseModel(Base):
__abstract__ = True
@declared_attr
def __tablename__(self):
"""
Ensures all tables have the same name as their models (below)
"""
return self.__name__.lower()
def to_dict(self):
"""
Helper method to convert any database row to dict
"""
return {c.name: getattr(self, c.name) for c in self.__table__.columns}
class Peer(BaseModel):
identifier = Column(String(32), primary_key=True)
hostname = Column(String, index=True, unique=True)
timestamp = Column(DateTime, index=True)
class Block(BaseModel):
height = Column(Integer, primary_key=True)
timestamp = Column(DateTime, index=True)
transactions = Column(PickleType)
previous_hash = Column(String(64))
proof = Column(String(64))
hash = Column(String(64))
class Config(BaseModel):
key = Column(String(64), primary_key=True, unique=True)
value = Column(PickleType)
def reset_db():
"""
Drops and Re-creates the Database
"""
Base.metadata.drop_all(bind=engine)
Base.metadata.create_all(bind=engine)

52
helpers.py Normal file
View File

@ -0,0 +1,52 @@
from hashlib import sha256
from sqlalchemy import func
from database import Config, Peer, db
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 value
if config_value != value and replace is True:
db.add(Config(key=key, value=value))
db.commit()
return value
return config_value
def get_config(key, default=None):
config = db.query(Config).filter_by(key=key).first()
if config:
return config.value
else:
return default
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)
def hash_block(block):
"""
Creates a SHA-256 hash_block of the fields for a Block
"""
byte_array = f"{block['height']}" \
f"{block['timestamp']}" \
f"{block['transactions']}" \
f"{block['previous_hash']}" \
f"{block['proof']}".encode()
return sha256(byte_array).hexdigest()

75
mining.py Normal file
View File

@ -0,0 +1,75 @@
import asyncio
import logging
import multiprocessing
from datetime import datetime
from helpers import hash_block
from tasks import we_should_still_be_mining
log = logging.getLogger('root.mining')
def proof_of_work(current_block, difficulty, event):
"""
Simple Proof of Work Algorithm
:param current_block: The partially complete block currently being mined
:param difficulty: The minimum number of leading zeros
"""
# String of 64 f's replaced with 3 leading zeros (if the difficulty is 3): 000fff...f
target = str.ljust("0" * difficulty, 64, "f")
guess_hash = hash_block(current_block)
while guess_hash > target:
# Check if we should still be mining
# if not event.is_set():
# raise Exception("STOP MINING")
current_block['timestamp'] = datetime.utcnow()
current_block['proof'] += 1
guess_hash = hash_block(current_block)
current_block['hash'] = guess_hash
return current_block
def miner(pipe, event):
while True:
task = pipe.recv()
log.debug(f"Received new mining task with difficulty {task['difficulty']}")
if task:
found_block = proof_of_work(task['block'], task['difficulty'], event)
pipe.send({'found_block': found_block})
async def mining_controller(app):
pipe, remote_pipe = multiprocessing.Pipe()
event = multiprocessing.Event()
# Spawn a new process consisting of the miner() function
# and send the right end of the pipe to it
process = multiprocessing.Process(target=miner, args=(remote_pipe, event))
process.start()
pipe.send({'block': app.blockchain.build_block(), 'difficulty': 5})
while True:
event.set()
# We'll check the pipe every 100 ms
await asyncio.sleep(0.1)
# Check if we should still be mining
if not we_should_still_be_mining():
event.clear()
if pipe.poll():
result = pipe.recv()
found_block = result['found_block']
app.blockchain.save_block(found_block)
log.info(f"Mined Block {found_block['height']} containing {len(found_block['transactions'])} transactions")
pipe.send({'block': app.blockchain.build_block(), 'difficulty': 5})

65
networking.py Normal file
View File

@ -0,0 +1,65 @@
import miniupnpc
import logging
log = logging.getLogger('root.networking')
class PortMapper(object):
def __init__(self):
client = miniupnpc.UPnP()
client.discoverdelay = 200
try:
log.info('Searching for Internet Gateway Devices... (timeout: %sms)', client.discoverdelay)
device_count = client.discover()
log.info('Found %s devices', device_count)
self.client = client
self.device_count = device_count
self.internal_ip = None
self.external_ip = None
self.external_port = None
except Exception as e:
log.error('An unexpected error occurred: %s', e)
self.client = None
def add_portmapping(self, internal_port, external_port, protocol, label=''):
if self.client is None:
log.error('No uPnP devices were found on the network')
return
try:
self.client.selectigd()
self.internal_ip = self.client.lanaddr
self.external_ip = self.client.externalipaddress()
log.info('Internal IP: %s', self.internal_ip)
log.info('External IP: %s', self.external_ip)
log.info('Attempting %s redirect: %s:%s -> %s:%s', protocol,
self.external_ip, external_port,
self.internal_ip, internal_port)
# Find an available port for the redirect
port_mapping = self.client.getspecificportmapping(external_port, protocol)
while port_mapping is None and external_port < 65536:
external_port += 1
port_mapping = self.client.getspecificportmapping(external_port, protocol)
success = self.client.addportmapping(external_port, protocol, self.internal_ip, internal_port, label, '')
if success:
log.info('Successful %s redirect: %s:%s -> %s:%s', protocol,
self.external_ip, external_port,
self.internal_ip, internal_port)
self.external_port = external_port
else:
log.error('Failed to map a port')
except Exception as e:
log.error('An unexpected error occurred: %s', e)

41
node.py Normal file
View File

@ -0,0 +1,41 @@
from sanic import Sanic
from sanic.response import json
from sqlalchemy import func
from database import Peer, db, reset_db
from tasks import initiate_node, peer_discovery
from mining import mining_controller
app = Sanic()
reset_db()
initiate_node(app)
app.add_task(peer_discovery(app))
app.add_task(mining_controller(app))
@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):
if request.method == 'GET':
return json(app.blockchain.current_transactions)
elif request.method == 'POST':
return json({"text": "thanks for your transaction"})
@app.route("/blocks")
async def blocks(request):
# height = request.parsed_args['height']
random_blocks = app.blockchain.get_blocks()
return json(random_blocks)
if __name__ == "__main__":
app.run(debug=False, host="0.0.0.0", port=8080)

64
tasks.py Normal file
View File

@ -0,0 +1,64 @@
import asyncio
import logging
from uuid import uuid4
import aiohttp
from blockchain import Blockchain
from database import db
from helpers import get_config, get_random_peers, set_config
from networking import PortMapper
log = logging.getLogger('root.tasks')
def initiate_node(app):
# Set up TCP Redirect (Port Forwarding)
# port_mapper = PortMapper()
# port_mapper.add_portmapping(8080, 8080, 'TCP', 'Electron')
# Set the identifier (unique Id) for our node (if it doesn't exist)
node_identifier = set_config(key='node_identifier', value=uuid4().hex)
# app.request_headers = {
# 'content-type': 'application/json',
# 'x-node-identifier': node_identifier,
# 'x-node-ip': port_mapper.external_ip,
# 'x-node-port': port_mapper.external_port,
# }
log.info('Node Identifier: %s', node_identifier)
# Add the Blockchain helper class to the app
app.blockchain = Blockchain()
async def peer_discovery(app):
"""
Ask random peers to return peers they know about
"""
while True:
peers = get_random_peers()
for peer in peers:
try:
response = await aiohttp.request('GET', 'peer.hostname', headers=app.request_headers)
print(f'Made request: {response.status}')
except asyncio.TimeoutError:
db.delete(peer)
db.commit()
print(f'{peer.hostname}: Deleted node')
await asyncio.sleep(10)
async def watch_blockchain(app):
while True:
print(f'TXN: {app.blockchain.current_transactions}')
await asyncio.sleep(2)
def we_should_still_be_mining():
return True

View File

@ -11,7 +11,7 @@ class BlockchainTestCase(TestCase):
self.blockchain = Blockchain()
def create_block(self, proof=123, previous_hash='abc'):
self.blockchain.new_block(proof, previous_hash)
self.blockchain.build_block(proof, previous_hash)
def create_transaction(self, sender='a', recipient='b', amount=1):
self.blockchain.new_transaction(
@ -101,4 +101,4 @@ class TestHashingAndProofs(BlockchainTestCase):
new_hash = hashlib.sha256(new_block_json).hexdigest()
assert len(new_hash) == 64
assert new_hash == self.blockchain.hash(new_block)
assert new_hash == self.blockchain.hash_block(new_block)