more cleanup
This commit is contained in:
parent
acb84b81e0
commit
8cd2a13747
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"hash": {
|
"hash_block": {
|
||||||
"sha256": "12bb22c1e036c3ea23ae6dfcd3682d0ff1f37b0e7817ba87aa3fe22ceb12ed9d"
|
"sha256": "12bb22c1e036c3ea23ae6dfcd3682d0ff1f37b0e7817ba87aa3fe22ceb12ed9d"
|
||||||
},
|
},
|
||||||
"host-environment-markers": {
|
"host-environment-markers": {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from hashlib import sha256
|
|
||||||
|
|
||||||
from database import Block, db
|
from database import Block, db
|
||||||
|
from helpers import hash_block
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger('root.blockchain')
|
logger = logging.getLogger('root.blockchain')
|
||||||
|
@ -12,12 +12,11 @@ class Blockchain:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.current_transactions = []
|
self.current_transactions = []
|
||||||
self.difficulty = 4
|
self.difficulty = 4
|
||||||
self.current_block = None
|
|
||||||
|
|
||||||
# Create the genesis block if necessary
|
# Create the genesis block if it doesn't exist
|
||||||
if not self.last_block:
|
if not self.last_block:
|
||||||
block = self.build_block()
|
block = self.build_block()
|
||||||
block['hash'] = self.hash(block)
|
block['hash'] = hash_block(block)
|
||||||
self.save_block(block)
|
self.save_block(block)
|
||||||
|
|
||||||
logger.info("✨ Created genesis block")
|
logger.info("✨ Created genesis block")
|
||||||
|
@ -50,8 +49,8 @@ class Blockchain:
|
||||||
while current_index < len(chain):
|
while current_index < len(chain):
|
||||||
block = chain[current_index]
|
block = chain[current_index]
|
||||||
|
|
||||||
# Check that the hash of the block is correct
|
# Check that the hash_block of the block is correct
|
||||||
if block['previous_hash'] != self.hash(last_block):
|
if block['previous_hash'] != self.hash_block(last_block):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Check that the Proof of Work is correct
|
# Check that the Proof of Work is correct
|
||||||
|
@ -116,16 +115,3 @@ class Blockchain:
|
||||||
:return: <Block>
|
:return: <Block>
|
||||||
"""
|
"""
|
||||||
return db.query(Block).order_by(Block.height.desc()).first()
|
return db.query(Block).order_by(Block.height.desc()).first()
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def hash(b):
|
|
||||||
"""
|
|
||||||
Creates a SHA-256 hash of the fields for a Block
|
|
||||||
"""
|
|
||||||
byte_array = f"{b['height']}" \
|
|
||||||
f"{b['timestamp']}" \
|
|
||||||
f"{b['transactions']}" \
|
|
||||||
f"{b['previous_hash']}" \
|
|
||||||
f"{b['proof']}".encode()
|
|
||||||
|
|
||||||
return sha256(byte_array).hexdigest()
|
|
||||||
|
|
|
@ -1,6 +1,3 @@
|
||||||
import json
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from sqlalchemy import Column, DateTime, Integer, PickleType, String, create_engine
|
from sqlalchemy import Column, DateTime, Integer, PickleType, String, create_engine
|
||||||
from sqlalchemy.ext.declarative import declarative_base, declared_attr
|
from sqlalchemy.ext.declarative import declarative_base, declared_attr
|
||||||
from sqlalchemy.orm import scoped_session, sessionmaker
|
from sqlalchemy.orm import scoped_session, sessionmaker
|
||||||
|
|
19
helpers.py
19
helpers.py
|
@ -1,4 +1,4 @@
|
||||||
from datetime import date, datetime
|
from hashlib import sha256
|
||||||
|
|
||||||
from sqlalchemy import func
|
from sqlalchemy import func
|
||||||
|
|
||||||
|
@ -11,12 +11,12 @@ def set_config(key, value, replace=False):
|
||||||
if config_value is None:
|
if config_value is None:
|
||||||
db.add(Config(key=key, value=value))
|
db.add(Config(key=key, value=value))
|
||||||
db.commit()
|
db.commit()
|
||||||
return
|
return value
|
||||||
|
|
||||||
if config_value != value and replace is True:
|
if config_value != value and replace is True:
|
||||||
db.add(Config(key=key, value=value))
|
db.add(Config(key=key, value=value))
|
||||||
db.commit()
|
db.commit()
|
||||||
return
|
return value
|
||||||
|
|
||||||
return config_value
|
return config_value
|
||||||
|
|
||||||
|
@ -39,11 +39,14 @@ def get_random_peers(limit=10):
|
||||||
return db.query(Peer).order_by(func.random()).limit(limit)
|
return db.query(Peer).order_by(func.random()).limit(limit)
|
||||||
|
|
||||||
|
|
||||||
def json_serializer(obj):
|
def hash_block(block):
|
||||||
"""
|
"""
|
||||||
JSON serializer for objects not serializable by default json code
|
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()
|
||||||
|
|
||||||
if isinstance(obj, (datetime, date)):
|
return sha256(byte_array).hexdigest()
|
||||||
return obj.isoformat()
|
|
||||||
raise TypeError("Type %s not serializable" % type(obj))
|
|
||||||
|
|
44
mining.py
44
mining.py
|
@ -1,9 +1,10 @@
|
||||||
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
import multiprocessing
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from hashlib import sha256
|
|
||||||
import signal
|
|
||||||
|
|
||||||
from blockchain import Blockchain
|
from helpers import hash_block
|
||||||
|
from tasks import we_should_still_be_mining
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger('root.mining')
|
log = logging.getLogger('root.mining')
|
||||||
|
@ -20,7 +21,7 @@ def proof_of_work(current_block, difficulty, event):
|
||||||
# String of 64 f's replaced with 3 leading zeros (if the difficulty is 3): 000fff...f
|
# String of 64 f's replaced with 3 leading zeros (if the difficulty is 3): 000fff...f
|
||||||
target = str.ljust("0" * difficulty, 64, "f")
|
target = str.ljust("0" * difficulty, 64, "f")
|
||||||
|
|
||||||
guess_hash = Blockchain.hash(current_block)
|
guess_hash = hash_block(current_block)
|
||||||
|
|
||||||
while guess_hash > target:
|
while guess_hash > target:
|
||||||
# Check if we should still be mining
|
# Check if we should still be mining
|
||||||
|
@ -28,18 +29,47 @@ def proof_of_work(current_block, difficulty, event):
|
||||||
# raise Exception("STOP MINING")
|
# raise Exception("STOP MINING")
|
||||||
current_block['timestamp'] = datetime.utcnow()
|
current_block['timestamp'] = datetime.utcnow()
|
||||||
current_block['proof'] += 1
|
current_block['proof'] += 1
|
||||||
guess_hash = Blockchain.hash(current_block)
|
guess_hash = hash_block(current_block)
|
||||||
|
|
||||||
current_block['hash'] = guess_hash
|
current_block['hash'] = guess_hash
|
||||||
return current_block
|
return current_block
|
||||||
|
|
||||||
|
|
||||||
def miner(pipe, event):
|
def miner(pipe, event):
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
task = pipe.recv()
|
task = pipe.recv()
|
||||||
log.info(f"Received new mining task with difficulty {task['difficulty']}")
|
log.debug(f"Received new mining task with difficulty {task['difficulty']}")
|
||||||
|
|
||||||
if task:
|
if task:
|
||||||
found_block = proof_of_work(task['block'], task['difficulty'], event)
|
found_block = proof_of_work(task['block'], task['difficulty'], event)
|
||||||
pipe.send({'found_block': found_block})
|
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})
|
||||||
|
|
3
node.py
3
node.py
|
@ -3,7 +3,8 @@ from sanic.response import json
|
||||||
from sqlalchemy import func
|
from sqlalchemy import func
|
||||||
|
|
||||||
from database import Peer, db, reset_db
|
from database import Peer, db, reset_db
|
||||||
from tasks import initiate_node, mining_controller, peer_discovery
|
from tasks import initiate_node, peer_discovery
|
||||||
|
from mining import mining_controller
|
||||||
|
|
||||||
|
|
||||||
app = Sanic()
|
app = Sanic()
|
||||||
|
|
99
tasks.py
99
tasks.py
|
@ -1,15 +1,12 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
import multiprocessing
|
|
||||||
import time
|
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
|
|
||||||
from blockchain import Blockchain
|
from blockchain import Blockchain
|
||||||
from database import db
|
from database import db
|
||||||
from helpers import get_config, set_config, get_random_peers
|
from helpers import get_config, get_random_peers, set_config
|
||||||
from mining import miner
|
|
||||||
from networking import PortMapper
|
from networking import PortMapper
|
||||||
|
|
||||||
|
|
||||||
|
@ -18,20 +15,18 @@ log = logging.getLogger('root.tasks')
|
||||||
|
|
||||||
def initiate_node(app):
|
def initiate_node(app):
|
||||||
# Set up TCP Redirect (Port Forwarding)
|
# Set up TCP Redirect (Port Forwarding)
|
||||||
port_mapper = PortMapper()
|
# port_mapper = PortMapper()
|
||||||
port_mapper.add_portmapping(8080, 8080, 'TCP', 'Electron')
|
# port_mapper.add_portmapping(8080, 8080, 'TCP', 'Electron')
|
||||||
|
|
||||||
# Set the identifier (unique Id) for our node
|
# Set the identifier (unique Id) for our node (if it doesn't exist)
|
||||||
node_identifier = get_config('node_identifier')
|
node_identifier = set_config(key='node_identifier', value=uuid4().hex)
|
||||||
if not node_identifier:
|
|
||||||
node_identifier = set_config(key='node_identifier', value=uuid4().hex)
|
|
||||||
|
|
||||||
app.request_headers = {
|
# app.request_headers = {
|
||||||
'content-type': 'application/json',
|
# 'content-type': 'application/json',
|
||||||
'x-node-identifier': node_identifier,
|
# 'x-node-identifier': node_identifier,
|
||||||
'x-node-ip': port_mapper.external_ip,
|
# 'x-node-ip': port_mapper.external_ip,
|
||||||
'x-node-port': port_mapper.external_port,
|
# 'x-node-port': port_mapper.external_port,
|
||||||
}
|
# }
|
||||||
|
|
||||||
log.info('Node Identifier: %s', node_identifier)
|
log.info('Node Identifier: %s', node_identifier)
|
||||||
|
|
||||||
|
@ -65,77 +60,5 @@ async def watch_blockchain(app):
|
||||||
await asyncio.sleep(2)
|
await asyncio.sleep(2)
|
||||||
|
|
||||||
|
|
||||||
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 we_should_still_be_mining():
|
def we_should_still_be_mining():
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
pipe.send({'block': app.blockchain.build_block(), 'difficulty': 5})
|
|
||||||
|
|
|
@ -101,4 +101,4 @@ class TestHashingAndProofs(BlockchainTestCase):
|
||||||
new_hash = hashlib.sha256(new_block_json).hexdigest()
|
new_hash = hashlib.sha256(new_block_json).hexdigest()
|
||||||
|
|
||||||
assert len(new_hash) == 64
|
assert len(new_hash) == 64
|
||||||
assert new_hash == self.blockchain.hash(new_block)
|
assert new_hash == self.blockchain.hash_block(new_block)
|
||||||
|
|
Loading…
Reference in New Issue