This commit is contained in:
Daniel van Flymen 2017-12-29 13:52:43 -05:00
parent bfe4de5016
commit fd7cf5e2f9
7 changed files with 90 additions and 77 deletions

View File

@ -1,8 +1,10 @@
import hashlib
import json
import logging
from datetime import datetime
from hashlib import sha256
from database import Block, db
from database import Block, db, DateTimeEncoder
from helpers import json_serializer
logger = logging.getLogger('root.blockchain')
@ -12,10 +14,14 @@ class Blockchain:
def __init__(self):
self.current_transactions = []
self.difficulty = 4
self.current_block = None
# Create the genesis block if necessary
if not self.get_blocks(0):
self.new_block(previous_hash='1', proof=100)
if not self.last_block:
block = self.build_block()
block['hash'] = self.hash(block)
self.save_block(block)
logger.info("✨ Created genesis block")
logger.info("Blockchain Initiated")
@ -59,31 +65,32 @@ class Blockchain:
return True
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
:param height: The Height of the new block
:return: New Block
"""
last_block = self.last_block
block = Block(
timestamp=datetime.now(),
transactions=self.current_transactions,
proof=proof,
previous_hash=previous_hash,
)
return {
'transactions': self.current_transactions,
'previous_hash': last_block.hash if last_block else 0,
'timestamp': datetime.now()
}
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:
"""
block = Block(**block_dict)
db.add(block)
db.commit()
# Reset the current list of transactions
self.current_transactions = []
return block
def new_transaction(self, sender, recipient, amount):
"""
Creates a new transaction to go into the next mined Block
@ -113,10 +120,7 @@ class Blockchain:
@staticmethod
def hash(block):
"""
Creates a SHA-256 hash of a Block
:param block: Block
Creates a SHA-256 hash of the fields for a Block
"""
block_bytes = block.to_json().encode()
return hashlib.sha256(block_bytes).hexdigest()
json_string = json.dumps(block, sort_keys=True, cls=DateTimeEncoder)
return sha256(json_string.encode()).hexdigest()

View File

@ -1,4 +1,6 @@
import json
from datetime import datetime
from hashlib import sha256
from sqlalchemy import Column, DateTime, Integer, PickleType, String, create_engine
from sqlalchemy.ext.declarative import declarative_base, declared_attr
@ -9,8 +11,6 @@ from sqlalchemy.orm import scoped_session, sessionmaker
engine = create_engine('sqlite:///electron.db')
db = scoped_session(sessionmaker(bind=engine))
from datetime import datetime
import json
class DateTimeEncoder(json.JSONEncoder):
def default(self, o):
@ -19,7 +19,13 @@ class DateTimeEncoder(json.JSONEncoder):
return json.JSONEncoder.default(self, o)
class BaseModel(object):
Base = declarative_base()
class BaseModel(Base):
__abstract__ = True
@declared_attr
def __tablename__(self):
"""
@ -40,15 +46,13 @@ class BaseModel(object):
return json.dumps(self.to_dict(), sort_keys=True, cls=DateTimeEncoder)
Base = declarative_base(cls=BaseModel)
class Peer(Base):
class Peer(BaseModel):
identifier = Column(String(32), primary_key=True)
hostname = Column(String, index=True, unique=True)
timestamp = Column(DateTime, index=True)
class Block(Base):
class Block(BaseModel):
height = Column(Integer, primary_key=True, autoincrement=True)
timestamp = Column(DateTime, index=True)
transactions = Column(PickleType)
@ -57,7 +61,7 @@ class Block(Base):
hash = Column(String(64))
class Config(Base):
class Config(BaseModel):
key = Column(String(64), primary_key=True, unique=True)
value = Column(PickleType)
@ -68,3 +72,4 @@ def reset_db():
"""
Base.metadata.drop_all(bind=engine)
Base.metadata.create_all(bind=engine)

View File

@ -1,3 +1,5 @@
from datetime import date, datetime
from sqlalchemy import func
from database import Config, Peer, db
@ -35,3 +37,13 @@ def get_random_peers(limit=10):
:return:
"""
return db.query(Peer).order_by(func.random()).limit(limit)
def json_serializer(obj):
"""
JSON serializer for objects not serializable by default json code
"""
if isinstance(obj, (datetime, date)):
return obj.isoformat()
raise TypeError("Type %s not serializable" % type(obj))

View File

@ -1,54 +1,47 @@
import logging
from datetime import datetime
from hashlib import sha256
import signal
from blockchain import Blockchain
log = logging.getLogger('root.mining')
def valid_proof(last_hash, proof, target):
"""
Validates the Proof
:param last_hash: Hash of the previous block
:param proof: Current Proof
:param target: Target Difficulty
:return: True if correct, False if not.
"""
guess = f'{last_hash}{proof}'.encode()
guess_hash = sha256(guess).hexdigest()
return guess_hash < target
def proof_of_work(last_hash, difficulty, event):
def proof_of_work(current_block, difficulty, event):
"""
Simple Proof of Work Algorithm
:param last_hash: The hash of the previous block
: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")
proof = 0
while valid_proof(last_hash, proof, target) is False:
current_block['timestamp'] = datetime.now()
current_block['proof'] = 0
guess_hash = Blockchain.hash(current_block)
while guess_hash > target:
# Check if we should still be mining
# if not event.is_set():
# raise Exception("STOP MINING")
proof += 1
return proof
current_block['timestamp'] = datetime.now()
current_block['proof'] += 1
guess_hash = Blockchain.hash(current_block)
current_block['hash'] = guess_hash
return current_block
def miner(right, event):
while True:
log.info(f'Waiting for task')
latest_task = right.recv()
log.info(f"Received new task for hash {latest_task['last_hash']} "
f"with difficulty {latest_task['difficulty']}")
task = right.recv()
log.info(f"Received new mining task with difficulty {task['difficulty']}")
if latest_task:
proof = proof_of_work(latest_task['last_hash'], latest_task['difficulty'], event)
right.send({'proof': proof, 'last_hash': latest_task['last_hash']})
if task:
found_block = proof_of_work(task['block'], task['difficulty'], event)
right.send({'found_block': found_block})

View File

@ -8,7 +8,7 @@ from tasks import initiate_node, mining_controller, peer_discovery
app = Sanic()
# reset_db()
reset_db()
initiate_node(app)
app.add_task(peer_discovery(app))

View File

@ -18,20 +18,20 @@ 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')
# port_mapper = PortMapper()
# port_mapper.add_portmapping(8080, 8080, 'TCP', 'Electron')
# Set the identifier (unique Id) for our node
node_identifier = get_config('node_identifier')
if not node_identifier:
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,
}
# 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)
@ -120,7 +120,7 @@ async def mining_controller(app):
process = multiprocessing.Process(target=miner, args=(right, event))
process.start()
left.send({'last_hash': 123, 'difficulty': 6})
left.send({'block': app.blockchain.build_block(), 'difficulty': 4})
while True:
event.set()
@ -134,9 +134,8 @@ async def mining_controller(app):
if left.poll():
result = left.recv()
proof = result['proof']
previous_hash = result['last_hash']
app.blockchain.new_block(proof, previous_hash)
last_block_hash = app.blockchain.hash(app.blockchain.last_block)
found_block = result['found_block']
left.send({'last_hash': last_block_hash, 'difficulty': 6})
app.blockchain.save_block(found_block)
left.send({'block': app.blockchain.build_block(), 'difficulty': 4})

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(