class Blockchain: def __init__(self, port = 5000, miner = True, unitTests = False): """Init the blockchain. """ # Initialize the properties. self._master_chain = [] self._branch_list = [] self._last_hash = None self._pending_transactions = [] self._difficulty = 4 self._miner = miner #Block confirmation request self._blocks_to_confirm = [] self._block_to_mine = None self._confirm_block = False #Incoming Block confirmation flag self._block_added = False #Incoming block added flag #ip = get('https://api.ipify.org').text ip = "127.0.0.1" self._ip = "{}:{}".format(ip,port) if unitTests : self._add_genesis_block() self.broadcast = Broadcast(set(), self._ip) #Creating mining thread if self._miner: print("Create mining thread") mining_thread = threading.Thread(target = self.mine,daemon=True) mining_thread.start() def _add_genesis_block(self): """Adds the genesis block to your blockchain. """ self._master_chain.append(Block(0, [], time.time(), "0")) self._last_hash = self._master_chain[-1].compute_hash() print("Genesis block added, hash :",self._last_hash) def bootstrap(self, address): """The bootstrap address serves as the initial entry point of the bootstrapping procedure. It will contact the specified address, download the peerlist, and start the bootstrapping procedure. """ print("BOOSTRAPING -------------") if(address == self._get_ip()): # Initialize the chain with the Genesis block. self._add_genesis_block() return # Get the list of peer from bootstrap node try: result = send_to_one(address, "peers") except exceptions.RequestException: print("Unable to bootstrap (connection failed to bootstrap node)") return peers = result.json()["peers"] for peer in peers: self.add_node(peer) self.add_node(address) if(self._get_ip() in peers): peers.remove(self._get_ip()) # Get all the blocks from a non-corrupted node hashes = {} for peer in self.get_peers(): hashes[peer] = send_to_one(peer, "addNode", {"address" : self._get_ip()}) if hashes: address = get_address_best_hash(hashes) result = send_to_one(address, "blockchain") chain = result.json()["chain"] # Reconstruct the chain init_chain = [] for block in chain: block = json.loads(block) transaction = [] for t in block["_transactions"]: transaction.append(Transaction(t["key"], t["value"], t["origin"])) new_block = Block(block["_index"], transaction, block["_timestamp"], block["_previous_hash"], block["_nonce"]) print("Bootstrap BLOCK:",new_block.compute_hash()) init_chain.append(new_block) self._master_chain = init_chain self._last_hash = init_chain[-1].compute_hash() print("Bootstrap complete. Blockchain is now {} blocks long".format(len(self._master_chain))) # [print(block.__dict__) for block in self._master_chain] return def add_node(self, peer): """ Add a node to the network. """ self.broadcast.add_peer(peer) def _get_ip(self): """ Get ip address of a node. """ return self._ip def _add_block(self,new_block): """ Add a block to the blockchain if it is valid Apply longest chain rule Constraint: Discard block if the parent is more than 1 generation away from the master chain """ #Check block validity if not new_block.proof(self._difficulty): print("Block has incorrect proof") return False new_block_hash = new_block.compute_hash() #Direct successors of last block from master node #Discard block if the parent is more than 1 generation away if new_block._previous_hash == self._master_chain[-1].compute_hash(): self._branch_list.append([new_block]) print("Block ID {} hash {} added to BRANCH".format(new_block._index, new_block_hash)) return True createBranch = False for branch in self._branch_list: if createBranch: break #If parent of new_block is last block of the branch, #add it to the branch if branch[-1].compute_hash() == new_block._previous_hash: branch.append(new_block) createBranch = True print("Block ID {} hash {} added to BRANCH".format(new_block._index, new_block_hash)) break #Else, copy the branch and add the new block to the copy for k, block in enumerate(branch): if new_block._previous_hash == block.compute_hash(): new_branch = copy.deepcopy(branch[:k+1]) new_branch.append(new_block) self._branch_list.append(new_branch) print("Block ID {} hash {} added to NEW BRANCH".format(new_block._index, new_block_hash)) createBranch = True break max_len_branch = sorted(self._branch_list, key=len)[-1] max_len = len(sorted(self._branch_list, key=len)[-1]) #Longest chain rule : part of branch longer or equal to 2 blocks get added if max_len >= 2: #Add all but one element to the master chain self._last_hash = max_len_branch[-1].compute_hash() self._master_chain.extend(max_len_branch[:-1]) #Remove all but one element from the list of branches self._branch_list = [[max_len_branch[-1]]] for block in max_len_branch[:-1]: print("Block ID {} hash {} added to MASTER".format(block._index, block.compute_hash())) return True return createBranch def _proof_of_work(self): """ Implement the proof of work algorithm Also check for block confirmation request from another Node """ #Reset nonce self._block_to_mine._nonce = 0 #Get the real hash of the block computed_hash = self._block_to_mine.compute_hash() #Find the nonce that computes the right block hash while not computed_hash.startswith('0' * self._difficulty): if not self._confirm_block: self._block_to_mine._change_nonce() computed_hash = self._block_to_mine.compute_hash() if self._block_added: self._block_added = False #Discard currently mined block return False #Broadcast block to other nodes self.broadcast.broadcast("block",json.dumps(self._block_to_mine.__dict__, sort_keys=True, cls=TransactionEncoder)) print("Mined block hash",computed_hash) self._last_hash = computed_hash return True def get_blocks(self): """ Returns all blocks from the chain. """ return self._master_chain def get_last_master_hash(self): """Returns the hash of the last block. """ return self._master_chain[-1].compute_hash() def get_peers(self): """ Returns all peers of the newtork. """ return self.broadcast.get_peers() def difficulty(self): """Returns the difficulty level. """ return self._difficulty def add_transaction(self, transaction, broadcast = True): """Adds a transaction to your current list of transactions, and broadcasts it to your Blockchain network. NB : If the `mine` method is called, it will collect the current list of transactions, and attempt to mine a block with those. """ # print("Added transaction" ,transaction.__dict__) self._pending_transactions.append(transaction) if broadcast: self.broadcast.broadcast("transaction",json.dumps(transaction.__dict__,sort_keys=True)) return def confirm_block(self,foreign_block): """Pass a block to be confirmed by the blockchain. Parameters: ---------- foreign_block: Block object """ if self._miner : self._confirm_block = True print("Confirming an incoming block with hash ", foreign_block.compute_hash()) if self._add_block(foreign_block): self._block_added = True print("Block confirmed by other node") local_block_tr = self._block_to_mine.get_transactions() for tr in foreign_block.get_transactions(): # Remove the incoming block's transaction from the pool if tr in self._pending_transactions: print(tr.key, tr.value) self._pending_transactions.remove(tr) #Remove the incoming block's transaction from the locally mined block transaction list if tr in local_block_tr: local_block_tr.remove(tr) #The transactions that were not added to the chain get put back in the pool for tr in local_block_tr: if tr not in self._pending_transactions: self._pending_transactions.append(tr) self._confirm_block = False return True else: #Block is not valid, we continue mining # print("Resume mining...") #Reset block confirmation fields self._confirm_block = False return False print("Block confirmed by other node") else: self._pending_transactions = [] return self._add_block(foreign_block) def mine(self): """Implements the mining procedure. """ while(True): if not self._pending_transactions: time.sleep(1) #Wait before checking new transactions else: input_tr = copy.deepcopy(self._pending_transactions) nb_transactions = len(input_tr) self._block_to_mine = Block(index=random.randint(1, sys.maxsize), transactions=input_tr, timestamp=time.time(), previous_hash=self._last_hash) #Remove the transactions that were inserted into the block del self._pending_transactions[:nb_transactions] # print("Processed {} transaction(s) in this block, {} pending".format(nb_transactions, len(self._pending_transactions))) if self._proof_of_work(): self._add_block(self._block_to_mine) def is_valid(self): """Checks if the current state of the blockchain is valid, meaning, are the sequence of hashes, and the proofs of the blocks correct? """ chain = self._master_chain last_block = chain[-1] previous_hash = last_block._previous_hash print("Previous hash",previous_hash) it = -1 while previous_hash != "0": #Check if proof is valid and if previous hashes match if(previous_hash != chain[it-1].compute_hash() or not chain[it].proof(self._difficulty)): return False it = it - 1 previous_hash = chain[it]._previous_hash return True