Skip to content

Commit

Permalink
Core: Final working code
Browse files Browse the repository at this point in the history
  • Loading branch information
meetvora committed May 1, 2017
1 parent 9c17b44 commit 4b9eee2
Show file tree
Hide file tree
Showing 4 changed files with 191 additions and 36 deletions.
77 changes: 69 additions & 8 deletions core/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from time import time
from hashlib import sha256
from functools import reduce
import operator

class UTXO(object):
__slots__ = ['sender', 'receiver', 'value', 'timestamp', 'id', 'transaction']
Expand Down Expand Up @@ -35,7 +36,7 @@ def __init__(self,utxos):
raise ValidityError("UTXOs of a given transaction must have same sender.")

def __str__(self):
return ''.join(map(lambda u: str(u.id), self.utxos)) + str(self.sender)
return str(self.sender) +': ' + '|'.join(map(lambda u: str(u.receiver), self.utxos))

def __repr__(self):
return self.sender + ': ' + ' '.join([utxo.id[:10] for utxo in self.utxos])
Expand All @@ -49,7 +50,7 @@ def __init__(self, prev_block, threshold, block):
self.previous_hash = prev_block.hash
except AttributeError:
self.previous_hash = None
self.height = 1
self.height = 0
self.timestamp = time()
self.threshold = threshold
self.block = block
Expand All @@ -69,6 +70,7 @@ def __str__(self):
class Block(object):
__slots__ = ['header', 'transactions', 'flags', 'chain', 'threshold', 'hash', 'signature', 'miner', 'node']
hash_variables = ['header', 'transactions', 'chain', 'flags', 'signature']
depth = 0

def __init__(self, chain, threshold = 4):
prev_block = chain.getHead()
Expand Down Expand Up @@ -106,16 +108,69 @@ class GenesisBlock(object):
def __init__(self):
self.header = BlockHeader(None, 0, self)
self.timestamp = time()
self.height = 0
self.hash = sha256(str(self.timestamp).encode('utf-8')).hexdigest()
self.hash = '0000' + sha256(str(self.timestamp).encode('utf-8')).hexdigest()[4:]
self.size = len(self.hash)
self.depth = 0

def __repr__(self):
try:
return "indieChain[%s] %s" %(self.header.height, self.hash[:10])
except:
return "indieChain[%s]" % self.header.height

class SummaryBlock(object):
__slots__ = ['depth', 'changes', 'header', 'blocks', 'hash', 'height']

def __init__(self, blocks, depth, prev_block):
self.header = BlockHeader(prev_block, 4, self)
self.depth = depth
self.changes = {}
self.createSummary(blocks)
self.blocks = [block.header.height for block in blocks]
self.hash = blocks[-1].hash
self.height = self.blocks[0]

def createSummary(self, blocks):
if all(isinstance(block, Block) for block in blocks):
utxos = []
for blk in blocks:
for transaction in blk.transactions:
utxos += transaction.utxos
outgoing = [(utxo.sender, utxo.value) for utxo in utxos]
incoming = [(utxo.receiver, utxo.value) for utxo in utxos]
senders = set([utxo.sender for utxo in utxos])
receivers = set([utxo.receiver for utxo in utxos])
for wallet in list(senders) + list(receivers):
self.changes[wallet] = 0
for sender in senders:
self.changes[sender] = -sum(map(lambda v: v[1], filter(lambda u: u[0] == sender, outgoing)))
for receiver in receivers:
self.changes[receiver] += sum(map(lambda v: v[1], filter(lambda u: u[0] == receiver, incoming)))
elif all(isinstance(block, SummaryBlock) for block in blocks):
all_keys = reduce(operator.add,[block.changes.keys() for block in blocks])
for key in all_keys:
self.changes[key] = 0
for block in blocks:
for key, value in block.changes.items():
self.changes[key] += value
else:
raise TypeError('Invalid typing of blocks')

def __repr__(self):
return 'Summary: [' + '|'.join(map(str, self.blocks)) +']'

class indieChain(object):
__slots__ = ['transactions', 'blocks']
__slots__ = ['transactions', 'blocks', 'freelen', 'base_pointers', 'end_pointers', 'summary_width']

def __init__(self):
def __init__(self, freelen=5, width=5):
self.blocks = [GenesisBlock()]
self.transactions = []
#base_pointer[0] points to first height of normal blocks, base_pointer[1] points to depth1 summary blocks.
#base_pointer[2] points to first dept2 summary blocks
self.base_pointers = [1]
self.end_pointers =[1]
self.freelen = freelen
self.summary_width = width

def getHead(self):
if self.blocks == []:
Expand All @@ -130,12 +185,18 @@ def validateBlock(block):
return (head.hash == block.header.previous_hash)

if validateBlock(block):
block.header.height = 1 + self.getHead().header.height
block.header.height = 1 + self.getHead().header.height
self.end_pointers[0] += 1
self.blocks.append(block)
self.transactions += block.transactions

def getGenesis(self):
return self.blocks[0]

def getIndexByHeight(self, h):
for index, block in enumerate(self.blocks):
if block.header.height == h:
return index

def __repr__(self):
return 'indieChain: ' + ' '.join([str(block.hash) for block in self.blocks][:10])
return 'indieChain: ' + ' '.join([str(block.hash)[:10] for block in self.blocks])
111 changes: 89 additions & 22 deletions core/nodes.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
# import network
# import dataStorage
# from core.merkle import *
#import network
from .utils import *
from .base import *

Expand All @@ -27,6 +25,11 @@ def makePayment(self, receiver_address, amount):
utxo = UTXO(self.address, receiver_address, amount)
return utxo

def getBalance(self):
incoming_amount = sum(receiver['data'].value for receiver in self.receiver_endpoint)
outgoing_amount = sum(sender['amount'] for sender in self.sender_endpoint)
return incoming_amount - outgoing_amount

def finalizeTransaction(self, utxos):
def balance(utxo):
return utxo['data'].value - utxo['used']
Expand Down Expand Up @@ -64,8 +67,8 @@ def balance(utxo):
self.sender_endpoint = sender_endpoint_copy
del transaction
raise TransactionError("Cannot make the required transaction, due to insufficient balance.")
#destination = network.getWallet(receiver_address)
#destination.receiveUTXO(utxo)
#network.receiveUTXO(receiver_address, utxo)
#should execute receiveUTXO on receiver
for utxo in utxos:
self.sender_endpoint.append({'data': utxo, 'amount': utxo.value})
self.signTransaction(transaction)
Expand Down Expand Up @@ -97,6 +100,7 @@ def __init__(self, chain):
self.chain = chain
self.pool = []
self.getNodePublicKey()
self.createBlock()

def getNodePublicKey(self):
try:
Expand All @@ -111,7 +115,11 @@ def getNodePublicKey(self):
return key.publickey()

def signBlock(self, block):
block.signature = pkcs1_15.new(self._key).sign(SHA256.new(block.hash.encode('utf-8')))
try:
block.signature = pkcs1_15.new(self._key).sign(SHA256.new(block.hash.encode('utf-8')))
except:
block.hash = sha256(str(self).encode('utf-8')).hexdigest()
block.signature = pkcs1_15.new(self._key).sign(SHA256.new(block.hash.encode('utf-8')))
block.node = self.id

def createBlock(self):
Expand All @@ -125,40 +133,63 @@ def addBlock(self):
block = self.current_block
self.signBlock(block)
#transmit block to peers
network.broadcastToMiners(block)
### network.broadcastToMiners(block)
#list of response received from Miners. In case block is correct, response is (MinerID, Nonce). In case of Fork,
#(MinerID, [<blocks>]]) is received & if invalid, (MinerID, 'INVALID') is the response.
#responseFromMiners must execute evaluateBlock() on miners which yields the value
responses = network.responseFromMiners()
responses = dict(response)
### responses = network.responseFromMiners()
responses = [(1, 540), (2, 540)]
responses = dict(responses)

if 'INVALID' in responses.values():
self.current_block = None
raise ValidityError("Block terminated due to invalidation from Miner")

if 'Incorrect Signature' in responses.values():
raise ValidityError("Signature not matched. Check keys in path/.rsa/")

temp_nonce = None
for value in responses.values():
if isinstace(value, int):
if isinstance(value, int):
temp_nonce = value
break
if all(value == temp_nonce for value in responses.values()):
block.save(temp_nonce)
network.TransmitToPeers(block)
self.signBlock(block)
self.chain.push(block)
self.createBlock()
### network.TransmitToPeers(block)
#executes receiveBlock on all peers
else:
lagBlocks = []
addedTX = []
for key, value in responses.items():
if isinstace(value, list):
lagBlocks += value
lagBlocks = list(set(lagBlocks))
# lagHead = filter(lambda u: ,lagBlocks)
#push the blocks correctly and update current_block.header.prev_hash

if isinstance(value, tuple):
addedTX += list(value)
if addedTX:
for tx in addedTX:
block.transactions.remove(tx)
raise MiningError('Excess transcations deleted. Retry again')
else:
lagBlocks = sorted(list(set(lagBlocks)), key=lambda u: u.header.height)
lagHead = lagBlocks[-1]
for blk in lagBlocks:
self.chain.push(blk)
block.header.prev_hash = lagHead.hash
block.header.height = lagHead.header.height + 1
for i, blk in enumerate(self.chain.blocks):
assert(i+1 == blk.header.height)
if i>0:
assert(blk.header.prev_hash == self.chain.blocks[i-1].hash)
raise MiningError('Forked branch merged. Retry again')
#push the blocks correctly and update current_block.header.prev_hash

def pushTransaction(self, transaction):
try:
self.current_block.addTransaction(transaction)
if len(self.current_block.transactions) >= MAX_TX_LIMIT:
if len(self.current_block.transactions) >= self.MAX_TX_LIMIT:
self.addBlock()
except:
self.createBlock()
Expand All @@ -167,7 +198,7 @@ def pushTransaction(self, transaction):
## or get network layer to do it
## for peer in peers:
## peer.receiveTransaction(transaction)
network.broadcastToPeers(transaction)
### network.broadcastToPeers(transaction)
# **CORRECT-> broadcast the transaction block to all it peers in Network Layer**

#listening service for transaction
Expand All @@ -184,31 +215,67 @@ def receiveTransaction(self, transaction):
#listening service for block
def receiveBlock(self, block):
if self.ROLE == 'M':
if self.anayseBlock(block):
if self.analyseBlock(block):
self.addBlock(block)
elif block not in self.chain.blocks:
if self.current_block:
self.chain.push(block)
current_block.header.prev_hash = block.hash
current_block.header.height = block.height + 1
network.TransmitToPeers(block)
### network.TransmitToPeers(block)
#executes receiveBlock on all peers

def __repr__(self):
return '<%s> %s' % (self.ROLE, self.id)

#summarization implementation
#invoked everytime maxdepth reached
def summarizeChain(self, base_level=0):
chain = self.chain
blocks = chain.blocks
max_depth = len(self.chain.base_pointers) - 1
current_gap = self.chain.end_pointers[base_level] - self.chain.base_pointers[base_level]
if current_gap > 2*chain.freelen-2:
start_block_index = self.chain.getIndexByHeight(chain.base_pointers[base_level])
summarizedBlock = SummaryBlock(blocks = blocks[start_block_index: start_block_index + chain.summary_width],\
depth = base_level+1, prev_block = blocks[start_block_index-1])
self.chain.blocks[start_block_index: start_block_index + chain.summary_width] = [summarizedBlock]
try:
self.chain.end_pointers[base_level+1] = chain.base_pointers[base_level]
except Exception as e:
self.chain.end_pointers.append(chain.base_pointers[base_level])
self.chain.base_pointers[base_level] += chain.summary_width
if max_depth < base_level + 1:
self.chain.base_pointers.append(summarizedBlock.height)



#current version of code expects the miner to be live throughout. Thereby implying persistance among miners
class Miner(Node):
ROLE = 'M'

def analyseBlock(self, block):
signerPublicKey = network.getNodePublicKey(block.node)
try:
signerPublicKey.verify(SHA256.new(block.hash.encode('utf-8')), block.signature)
except (ValueError, TypeError):
return False

if block.flags == 0x11:
if not reduce(lambda x, y: x and y, map(verifyTransaction, block.transactions)):
return False
return True

#listening service corresponding to getForkedBlocks
def evaluateBlock(self, block):
assert(isinstance(block, Block))
if not block.nonce:
addedTX = []
for transaction in block.transactions:
if transaction in self.chain.transactions:
return 'INVALID'
addedTX.append(transaction)
if addedTX:
return tuple(addedTX)

signerPublicKey = network.getNodePublicKey(block.node)
try:
Expand Down Expand Up @@ -238,7 +305,7 @@ def evaluateBlock(self, block):
@classmethod
def generateNonce(block):
temp_block = block
for i in range(5*10**6):
for i in xrange(10**9):
temp_block.header.nonce = i
if sha256(str(temp_block).encode('utf-8')).hexdigest()[:block.threshold] == '0'*block.threshold:
return i
Expand All @@ -251,7 +318,7 @@ def addBlock(self, block):

@classmethod
def verifyTransaction(transaction):
signerPublicKey = network.getNodePublicKey(wallet_address = transaction.error)
signerPublicKey = network.getNodePublicKey(wallet_address = transaction.sender)
#returns publickey of node associated with the wallet address
try:
signerPublicKey.verify(SHA256.new(str(transaction).encode('utf-8')), transaction.signature)
Expand Down
3 changes: 0 additions & 3 deletions core/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,5 @@ class TransactionError(Exception):
class ValidityError(Exception):
pass

class UnmatchedHeadError(Exception):
pass

class MiningError(Exception):
pass
Loading

0 comments on commit 4b9eee2

Please sign in to comment.