def start_mining(): while True: print("Start mining new block") initial_time = time() block_template = basic_request('getblocktemplate') block_template = base64.b64decode(block_template.encode()) header = Header() header.deserialize(block_template) print( "Got template. Block height %d. Block target %d (* 2**220). Average hashes for block %d. Block timestamp %d" % (header.height, header.target / 2**220, 2**256 / header.target, header.timestamp)) partial_template = block_template[:-16] nonce = 0 next_level = 4096 while not check_solution(partial_template, nonce, header.target): nonce += 1 if not nonce % next_level: next_level *= 2 print("Nonce reached %d" % nonce) final_time = time() print("Get solution. Nonce = %d. Hashrate %d H/s" % (nonce, int(nonce / (final_time - initial_time)))) solution = partial_template + nonce.to_bytes(16, 'big') encoded_solution = base64.b64encode(solution).decode() res = basic_request('validatesolution', [encoded_solution]) print("Submitted block. Result %s" % res)
def process_find_common_root(message, node_info, rtx, core): #node_info is ignored, added to unify process_metadata function sugnature try: serialized_header = message["serialized_header"] header = Header() header.deserialize_raw(serialized_header) except: raise DOSException() result = [] for pointer in [header.hash]+header.popow.pointers: if not core.storage_space.headers_storage.has(pointer, rtx=rtx): result.append(HEADERSTATE.UNKNOWN) continue ph = core.storage_space.headers_storage.get(pointer, rtx=rtx) if not ph.connected_to_genesis: result.append(HEADERSTATE.ISOLATED) continue if core.storage_space.headers_manager.find_ancestor_with_height(core.storage_space.blockchain.current_tip(rtx=rtx), ph.height, rtx=rtx) == pointer: result.append(HEADERSTATE.MAINCHAIN) continue result.append(HEADERSTATE.INFORK) core.send_to_subprocess(message['sender'], \ {"action":"find common root response", "header_hash":header.hash, "flags_num": len(result), "known_headers": b"".join([i.to_bytes(1,"big") for i in result]), "id":message['id'], "node": message["node"] })
def process_solution(solution_type, message, wtx, core): try: if solution_type == "block template": header = Header() header.deserialize(message["solved template"]) solved_block = core.storage_space.mempool_tx.get_block_by_header_solution( header) elif solution_type == "work": nonce, partial_work = message['nonce'], message['partial_hash'] solved_block = core.storage_space.mempool_tx.work_block_assoc[ partial_work] solved_block.header.nonce = nonce result = add_solved_block(solved_block, wtx, core) if result == "stale": core.send_to_subprocess(message["sender"], { "id": message["id"], "result": "Stale" }) core.logger.error("Stale work submitted: height %d" % (header.height)) return elif result == "accepted": core.send_to_subprocess(message["sender"], { "id": message["id"], "result": "Accepted" }) except Exception as e: core.logger.error("Wrong block solution %s" % str(e)) core.send_to_subprocess(message["sender"], { "id": message["id"], "error": str(e), 'result': 'error' })
def process_new_headers(message, node_info, wtx, core): dupplication_header_dos = False #TODO grammatical typo? try: serialized_headers = message["headers"] num = message["num"] header = None for i in range(num): header = Header() serialized_headers = header.deserialize_raw(serialized_headers) if not core.storage_space.headers_storage.has(header.hash, rtx=wtx): core.storage_space.headers_manager.add_header(header, wtx=wtx) if not i % 20: core.notify( "best header", core.storage_space.headers_manager.best_header_height) else: dupplication_header_dos = True if ("common_root" in node_info) and ("long_reorganization" in node_info["common_root"]): if core.storage_space.headers_manager.get_best_tip( )[0] == header.hash: #not reorg anymore node_info["common_root"].pop("long_reorganization", None) our_tip_hash = core.storage_space.blockchain.current_tip( rtx=wtx) core.storage_space.blockchain.update( wtx=wtx, reason="downloaded new headers") send_tip_info(node_info=node_info, our_tip_hash=our_tip_hash, rtx=wtx, core=core) elif node_info["common_root"][ "long_reorganization"] == header.height: request_num = min( 256, node_info["height"] - core.storage_space.headers_storage.get( node_info["common_root"]["root"], rtx=wtx).height) send_next_headers_request(header.hash, request_num, message["node"], send=partial(core.send_to_subprocess, "NetworkManager")) if node_info["height"] - header.height > request_num: node_info["common_root"][ "long_reorganization"] = header.height + request_num else: node_info["common_root"].pop("long_reorganization", None) core.storage_space.blockchain.update(wtx=wtx, reason="downloaded new headers") except Exception as e: raise DOSException() #TODO add info
def process_new_headers(message): dupplication_header_dos = False try: serialized_headers = message["headers"] num = message["num"] for i in range(num): header = Header() serialized_headers = header.deserialize_raw(serialized_headers) if header.hash in storage_space.headers_storage: dupplication_header_dos = True continue storage_space.headers_manager.add_header(header) storage_space.blockchain.update(reason="downloaded new headers") except Exception as e: raise e
def generate_genesis(tx, storage_space): ''' 1. spend inputs and add outputs and excesses from tx to storage 2. calc new mercles 3. generate header 4. rollback outputs ''' storage = storage_space.txos_storage excesses = storage_space.excesses_storage merkles = storage.apply_tx_get_merkles_and_rollback(tx) + [ excesses.apply_tx_get_merkles_and_rollback(tx) ] popow = PoPoW([]) votedata = VoteData() target = initial_target header = Header(height=0, supply=tx.coinbase.value, merkles=merkles, popow=popow, votedata=votedata, timestamp=int(time()), target=target, version=int(1), nonce=b"\x00" * 16) tx_skeleton = TransactionSkeleton(tx=tx) new_block = Block(storage_space, header, tx_skeleton) return new_block
def generate_genesis(tx, storage_space, wtx): ''' 1. spend inputs and add outputs and excesses from tx to storage 2. calc new mercles 3. generate header 4. rollback outputs ''' storage = storage_space.txos_storage excesses = storage_space.excesses_storage exc_merkle = excesses.apply_block_tx_get_merkles_and_rollback( tx, wtx=wtx ) # it should be calced first, since we nned to calc address_excess_num_index merkles = storage.apply_block_tx_get_merkles_and_rollback( tx, wtx=wtx) + [exc_merkle] popow = PoPoW([]) votedata = VoteData() target = initial_target full_offset = tx.mixer_offset header = Header(height=0, supply=tx.coinbase.value, full_offset=full_offset, merkles=merkles, popow=popow, votedata=votedata, timestamp=int(time()), target=target, version=int(1), nonce=b"\x00" * 16) tx_skeleton = TransactionSkeleton(tx=tx) new_block = Block(storage_space, header, tx_skeleton) return new_block
def start_mining(): while True: print("Start mining new block") initial_time = time() height_check_time = initial_time basic_nonce = randint(0, int(256**3)) block_template = basic_request('getblocktemplate') block_template = base64.b64decode(block_template.encode()) header = Header() header.deserialize(block_template) print("Got template. Block height %d. Block target %d (* 2**220). Average hashes for block %d. Block timestamp %d"%(header.height,header.target/2**220, 2**256/header.target, header.timestamp)) partial_template = block_template[:-16] nonce = 0 step = next_level = 16 update_block = False height, target = header.height, header.target partial_hash = header.partial_hash solution_found = False while not solution_found: res = pp_handler.light_search(height, partial_hash, target.to_bytes(32,"big"), start_nonce = basic_nonce+nonce, iterations = next_level, step=step) solution_found = res['solution_found'] if res['solution_found']: final_nonce, final_hash = res['nonce'], res['final_hash'] break nonce += next_level if time()-height_check_time>5: height_check_time = time() if height<=get_height(): print("New block on network") update_block = True break if not nonce%next_level: next_level*=2 print("Nonce reached %d"%nonce) final_time = time() if update_block: print("Hashrate %d H/s"%(int(nonce/(final_time-initial_time)))) continue print("Get solution. Nonce = %d (final_nonce %d). Hashrate %d H/s"%(nonce, final_nonce, int(nonce/(final_time-initial_time)))) solution =partial_template +final_nonce.to_bytes(8,'big') encoded_solution = base64.b64encode(solution).decode() res = basic_request('validatesolution', [encoded_solution]) print("Submitted block. Result %s"%res)
def generate_block_template(tx, storage_space, get_tx_from_mempool=True, timestamp=None): ''' Generate block template: block is correct but nonce (by default) is equal to zero. Thus difficulty target (almost always) isn't met. arguments: tx [mandatory]: transaction which contains coinbase output. It also may contain other inputs and outputs. storage_space [mandatory] : - get_tx_from_mempool [optional, default True]: if get_tx_from_mempool, transaction from mempool will be merged to block_transaction. If this merge will produce invalid tx (for instance tx from mempool spends the same inputs as tx with coinbase), tx from mempool will be discarded. Inner logic: 1. apply block_tx to txos_storage and excesses_storage 2. calc new merkles 3. generate header with new merkles 4. generate block by appending tx_skeleton and new header 5. rollback block_tx ''' storage = storage_space.txos_storage excesses = storage_space.excesses_storage current_block = storage_space.blocks_storage[ storage_space.blockchain.current_tip] if get_tx_from_mempool: try: tx = tx.merge(storage_space.mempool_tx.give_tx()) except: pass merkles = storage.apply_tx_get_merkles_and_rollback(tx) + [ excesses.apply_tx_get_merkles_and_rollback(tx) ] popow = current_block.header.next_popow() #We subtract relay fee, since coinbase value contain relay fees, but it isn't new money, but redistribution supply = current_block.header.supply + tx.coinbase.value - tx.calc_new_outputs_fee( ) - tx.relay_fee height = current_block.header.height + 1 votedata = VoteData() target = next_target(current_block.hash, storage_space.headers_storage) if not timestamp: timestamp = max( int(time()), storage_space.headers_storage[ storage_space.blockchain.current_tip].timestamp + 1) header = Header(height=height, supply=supply, merkles=merkles, popow=popow, votedata=votedata, timestamp=timestamp, target=target, version=int(1), nonce=b"\x00" * 16) tx_skeleton = TransactionSkeleton(tx=tx) new_block = Block(storage_space, header, tx_skeleton) return new_block
def process_find_common_root(message, send_message): serialized_header = message["serialized_header"] header = Header() header.deserialize_raw(serialized_header) result = [] for pointer in [header.hash] + header.popow.pointers: if not pointer in storage_space.headers_storage: result.append(UNKNOWN) continue ph = storage_space.headers_storage[pointer] if not ph.connected_to_genesis: result.append(ISOLATED) continue if storage_space.headers_manager.find_ancestor_with_height( storage_space.blockchain.current_tip, ph.height) == pointer: result.append(MAINCHAIN) continue result.append(INFORK) send_message(message['sender'], \ {"action":"find common root response", "header_hash":header.hash, "flags_num": len(result), "known_headers": b"".join([i.to_bytes(1,"big") for i in result]), "id":message['id'], "node": message["node"] })
def header(self): try: return self._header except: self._header = Header() return self._header
def __init__(self, storage_space, header=None, transaction_skeleton=None): self._header = header if header else Header() self.transaction_skeleton = transaction_skeleton if transaction_skeleton else TransactionSkeleton( ) self.tx = None self.storage_space = storage_space
def generate_block_template(tx, storage_space, wtx, get_tx_from_mempool=True, timestamp=None, dev_reward_vote=b"\x00"): ''' Generate block template: block is correct but nonce (by default) is equal to zero. Thus difficulty target (almost always) isn't met. arguments: tx [mandatory]: transaction which contains coinbase output. It also may contain other inputs and outputs. storage_space [mandatory] : - get_tx_from_mempool [optional, default True]: if get_tx_from_mempool, transaction from mempool will be merged to block_transaction. If this merge will produce invalid tx (for instance tx from mempool spends the same inputs as tx with coinbase), tx from mempool will be discarded. Inner logic: 1. apply block_tx to txos_storage and excesses_storage 2. calc new merkles 3. generate header with new merkles 4. generate block by appending tx_skeleton and new header 5. rollback block_tx ''' storage = storage_space.txos_storage excesses = storage_space.excesses_storage current_block = storage_space.blocks_storage.get( storage_space.blockchain.current_tip(rtx=wtx), rtx=wtx) if get_tx_from_mempool: try: tx = tx.merge(storage_space.mempool_tx.give_tx(), rtx=wtx) except: pass exc_merkle = excesses.apply_block_tx_get_merkles_and_rollback( tx, wtx=wtx ) # it should be calced first, since we nned to calc address_excess_num_index merkles = storage.apply_block_tx_get_merkles_and_rollback( tx, wtx=wtx) + [exc_merkle] popow = current_block.header.next_popow() supply = current_block.header.supply + tx.minted_value - tx.calc_new_outputs_fee( ) height = current_block.header.height + 1 votedata = VoteData() target = next_target(current_block.hash, storage_space.headers_storage, rtx=wtx) full_offset = sum_offset(current_block.header.full_offset, tx.mixer_offset) if not timestamp: timestamp = max( int(time()), storage_space.headers_storage.get( storage_space.blockchain.current_tip(rtx=wtx), rtx=wtx).timestamp + 1) header = Header(height=height, supply=supply, full_offset=full_offset, merkles=merkles, popow=popow, votedata=votedata, timestamp=timestamp, target=target, version=int(1), nonce=b"\x00" * 16) tx_skeleton = TransactionSkeleton(tx=tx) new_block = Block(storage_space, header, tx_skeleton) return new_block
def core_loop(syncer, config): message_queue = syncer.queues['Blockchain'] init_storage_space(config) nodes = {} set_ask_for_blocks_hook(storage_space.blockchain, message_queue) set_ask_for_txouts_hook(storage_space.blocks_storage, message_queue) requests = {} message_queue.put({"action": "give nodes list reminder"}) def send_message(destination, message): if not 'id' in message: message['id'] = uuid4() if not 'sender' in message: message['sender'] = "Blockchain" syncer.queues[destination].put(message) def send_to_nm(message): send_message("NetworkManager", message) logger.debug("Start of core loop") while True: sleep(0.05) put_back_messages = [] while not message_queue.empty(): message = message_queue.get() if 'time' in message and message['time'] > time( ): # delay this message put_back_messages.append(message) continue logger.info("Processing message %s" % message) if not 'action' in message: #it is response if message['id'] in requests: # response is awaited if requests[message['id']] == "give nodes list": requests.pop(message['id']) message_queue.put({ "action": "take nodes list", "nodes": message["result"] }) else: pass #Drop continue try: if message["action"] == "take the headers": process_new_headers(message) if message["action"] == "take the blocks": initial_tip = storage_space.blockchain.current_tip process_new_blocks(message) after_tip = storage_space.blockchain.current_tip if not after_tip == initial_tip: notify_all_nodes_about_new_tip(nodes, send_to_nm) if message["action"] == "take the txos": process_new_txos(message) if message["action"] == "give blocks": process_blocks_request(message, send_message) if message["action"] == "give next headers": process_next_headers_request(message, send_message) if message["action"] == "give txos": process_txos_request(message, send_message) if message["action"] == "find common root": process_find_common_root(message, send_message) if message["action"] == "find common root response": process_find_common_root_reponse(message, nodes[message["node"]], send_message) if message["action"] == "give TBM transaction": process_tbm_tx_request(message, send_message) if message["action"] == "take TBM transaction": process_tbm_tx(message, send_to_nm, nodes) if message["action"] == "give tip height": send_message( message["sender"], { "id": message["id"], "result": storage_space.blockchain.current_height }) if message["action"] == "take tip info": if not message["node"] in nodes: nodes[message["node"]] = {'node': message["node"]} process_tip_info(message, nodes[message["node"]], send=send_to_nm) except DOSException as e: logger.info("DOS Exception %s" % str(e)) #raise e #TODO send to NM except Exception as e: raise e if message["action"] == "give block template": block = storage_space.mempool_tx.give_block_template() ser_head = block.header.serialize() send_message(message["sender"], { "id": message["id"], "result": ser_head }) if message["action"] == "take solved block template": try: initial_tip = storage_space.blockchain.current_tip header = Header() header.deserialize(message["solved template"]) solved_block = storage_space.mempool_tx.get_block_by_header_solution( header) storage_space.headers_manager.add_header( solved_block.header) storage_space.headers_manager.context_validation( solved_block.header.hash) solved_block.non_context_verify() storage_space.blockchain.add_block(solved_block) send_message(message["sender"], { "id": message["id"], "result": "Accepted" }) after_tip = storage_space.blockchain.current_tip if not after_tip == initial_tip: notify_all_nodes_about_new_tip(nodes, send_to_nm) except Exception as e: raise e send_message(message["sender"], { "id": message["id"], "error": str(e) }) if message["action"] == "get confirmed balance stats": if storage_space.mempool_tx.key_manager: stats = storage_space.mempool_tx.key_manager.get_confirmed_balance_stats( storage_space.utxo_index, storage_space.txos_storage, storage_space.blockchain.current_height) send_message(message["sender"], { "id": message["id"], "result": stats }) else: send_message(message["sender"], { "id": message["id"], "error": "No registered key manager" }) if message["action"] == "get confirmed balance list": if storage_space.mempool_tx.key_manager: _list = storage_space.mempool_tx.key_manager.get_confirmed_balance_list( storage_space.utxo_index, storage_space.txos_storage, storage_space.blockchain.current_height) send_message(message["sender"], { "id": message["id"], "result": _list }) else: send_message(message["sender"], { "id": message["id"], "error": "No registered key manager" }) if message["action"] == "give new address": if storage_space.mempool_tx.key_manager: texted_address = storage_space.mempool_tx.key_manager.new_address( ).to_text() send_message(message["sender"], { "id": message["id"], "result": texted_address }) else: send_message(message["sender"], { "id": message["id"], "error": "No registered key manager" }) if message["action"] == "give private key": if storage_space.mempool_tx.key_manager: km = storage_space.mempool_tx.key_manager a = Address() a.from_text(message["address"]) serialized_pk = km.priv_by_address(a).serialize() send_message(message["sender"], { "id": message["id"], "result": serialized_pk }) else: send_message(message["sender"], { "id": message["id"], "error": "No registered key manager" }) if message["action"] == "take private key": if storage_space.mempool_tx.key_manager: km = storage_space.mempool_tx.key_manager pk = PrivateKey() pk.deserialize(message['privkey']) km.add_privkey(pk) send_message(message["sender"], { "id": message["id"], "result": "imported" }) else: send_message(message["sender"], { "id": message["id"], "error": "No registered key manager" }) if message["action"] == "give synchronization status": our_height = storage_space.blockchain.current_height best_known_header = storage_space.headers_manager.best_header_height try: best_advertised_height = max([ nodes[node]["height"] for node in nodes if "height" in nodes[node] ]) except: best_advertised_height = None send_message( message["sender"], { "id": message["id"], "result": { 'height': our_height, 'best_known_header': best_known_header, 'best_advertised_height': best_advertised_height } }) if message["action"] == "send to address": value = int(message["value"]) taddress = message["address"] a = Address() a.from_text(taddress) if storage_space.mempool_tx.key_manager: _list = storage_space.mempool_tx.key_manager.get_confirmed_balance_list( storage_space.utxo_index, storage_space.txos_storage, storage_space.blockchain.current_height) list_to_spend = [] summ = 0 for address in _list: for texted_index in _list[address]: if summ > value: continue if isinstance(_list[address][texted_index], int): _index = base64.b64decode( texted_index.encode()) utxo = storage_space.txos_storage.confirmed[ _index] if not utxo.lock_height <= storage_space.blockchain.current_height: continue list_to_spend.append(utxo) summ += _list[address][texted_index] if summ < value: send_message( message["sender"], { "id": message["id"], "error": "Not enough matured coins" }) tx = Transaction( txos_storage=storage_space.txos_storage, key_manager=storage_space.mempool_tx.key_manager) for utxo in list_to_spend: tx.push_input(utxo) tx.add_destination((a, value)) tx.generate() tx.verify() storage_space.mempool_tx.add_tx(tx) tx_skel = TransactionSkeleton(tx=tx) notify_all_nodes_about_tx(tx_skel.serialize( rich_format=True, max_size=40000), nodes, send_to_nm, _except=[], mode=1) send_message(message["sender"], { "id": message["id"], "result": "generated" }) else: send_message(message["sender"], { "id": message["id"], "error": "No registered key manager" }) #message from core_loop if message["action"] == "check txouts download status": txos = message["txos_hashes"] to_be_downloaded = [] for txo in txos: if not storage_space.txos_storage.known(txo): to_be_downloaded.append(txo) if not to_be_downloaded: continue #We are good, txouts are already downloaded already_asked_nodes = message["already_asked_nodes"] asked = False for node_params in nodes: node = nodes[node_params] if node in already_asked_nodes: continue already_asked_nodes += [node] send_to_nm({ "action": "give txos", "txos_hashes": b"".join(to_be_downloaded), "num": len(to_be_downloaded), "id": str(uuid4()), "node": node_params }) new_message = { "action": "check txouts download status", "txos_hashes": to_be_downloaded, "already_asked_nodes": already_asked_nodes, "id": str(uuid4()), "time": int(time() + 300) } asked = True put_back_messages.append(new_message) break if not asked: #We already asked all applicable nodes message["time"] = int(time()) + 3600 message["already_asked_nodes"] = [] put_back_messages.append( message) # we will try to ask again in an hour #message from core_loop if message["action"] == "check blocks download status": #TODO download many blocks at once block_hashes = message["block_hashes"] to_be_downloaded = [] lowest_height = 1e10 for block_hash in block_hashes: if block_hash in storage_space.blocks_storage: continue #We are good, block already downloaded if not block_hash in storage_space.blockchain.awaited_blocks: continue #For some reason we don't need this block anymore to_be_downloaded.append(block_hash) if storage_space.headers_storage[ block_hash].height < lowest_height: lowest_height = storage_space.headers_storage[ block_hash].height already_asked_nodes = message["already_asked_nodes"] asked = False for node_params in nodes: node = nodes[node_params] if node in already_asked_nodes: continue if node["height"] < lowest_height: continue already_asked_nodes += [node] send_to_nm({ "action": "give blocks", "block_hashes": bytes(b"".join(block_hashes)), 'num': len(block_hashes), "id": str(uuid4()), "node": node_params }) new_message = { "action": "check blocks download status", "block_hashes": to_be_downloaded, "already_asked_nodes": already_asked_nodes, "id": str(uuid4()), "time": int(time() + 300) } asked = True put_back_messages.append(new_message) break if not asked: #We already asked all applicable nodes message["time"] = int(time()) + 3600 message["already_asked_nodes"] = [] put_back_messages.append( message) # we will try to ask again in an hour if message["action"] == "take nodes list": for node in message["nodes"]: if not node in nodes: #Do not overwrite nodes[node] = {"node": node} disconnected_nodes = [] for existing_node in nodes: if not existing_node in message["nodes"]: disconnected_nodes.append(existing_node) for dn in disconnected_nodes: nodes.pop(dn) if message["action"] == "give nodes list reminder": _id = str(uuid4()) send_to_nm({ "action": "give nodes list", "sender": "Blockchain", "id": _id }) requests[_id] = "give nodes list" put_back_messages.append({ "action": "give nodes list reminder", "time": int(time()) + 3 }) for _message in put_back_messages: message_queue.put(_message) try: check_sync_status(nodes, send_to_nm) except Exception as e: logger.error(e)