def VE(self, data=None): """ Version If data is None then sends the version & genesis_prev_headerhash. Otherwise, process the content of data and incase of non matching, genesis_prev_headerhash, it disconnects the odd peer. :return: """ if not data: version_details = { 'version': config.dev.version_number, 'genesis_prev_headerhash': config.dev.genesis_prev_headerhash } self.transport.write(self.wrap_message('VE', helper.json_encode(version_details))) else: try: data = helper.json_decode(data) self.version = str(data['version']) logger.info('%s version: %s | genesis prev_headerhash %s', self.transport.getPeer().host, data['version'], data['genesis_prev_headerhash']) if data['genesis_prev_headerhash'] == config.dev.genesis_prev_headerhash: return logger.warning('%s genesis_prev_headerhash mismatch', self.conn_identity) logger.warning('Expected: %s', config.dev.genesis_prev_headerhash) logger.warning('Found: %s', data['genesis_prev_headerhash']) except Exception as e: logger.error('Peer Caused Exception %s', self.conn_identity) logger.exception(e) self.transport.loseConnection() return
def BM(self, data=None): # blockheight map for synchronisation and error correction prior to POS cycle resync.. """ Blockheight Map Simply maps the peer with their respective blockheight. If no data is provided in parameter, the node sends its own current blockheight. :return: """ if not data: logger.info('<<< Sending block_map %s', self.transport.getPeer().host) z = {'block_number': self.factory.chain.m_blockchain[-1].blockheader.blocknumber, 'headerhash': self.factory.chain.m_blockchain[-1].blockheader.headerhash} self.transport.write(self.wrap_message('BM', helper.json_encode(z))) return else: logger.info('>>> Receiving block_map') z = helper.json_decode(data) block_number = z['block_number'] headerhash = z['headerhash'].encode('latin1') i = [block_number, headerhash, self.transport.getPeer().host] logger.info('%s', i) if i not in self.factory.chain.blockheight_map: self.factory.chain.blockheight_map.append(i) return
def BM( self, data=None ): # blockheight map for synchronisation and error correction prior to POS cycle resync.. if not data: logger.info('<<< Sending block_map %s', self.transport.getPeer().host) z = { 'block_number': self.factory.chain.m_blockchain[-1].blockheader.blocknumber, 'headerhash': self.factory.chain.m_blockchain[-1].blockheader.headerhash } self.transport.write(self.wrap_message('BM', helper.json_encode(z))) return else: logger.info('>>> Receiving block_map') z = helper.json_decode(data) block_number = z['block_number'] headerhash = z['headerhash'].encode('latin1') i = [block_number, headerhash, self.transport.getPeer().host] logger.info('%s', i) if i not in self.factory.chain.blockheight_map: self.factory.chain.blockheight_map.append(i) return
def VE(self, data=None): if not data: version_details = { 'version': config.dev.version_number, 'genesis_prev_headerhash': config.dev.genesis_prev_headerhash } self.transport.write( self.wrap_message('VE', helper.json_encode(version_details))) else: try: data = helper.json_decode(data) self.version = str(data['version']) logger.info('%s version: %s | genesis prev_headerhash %s', self.transport.getPeer().host, data['version'], data['genesis_prev_headerhash']) if data['genesis_prev_headerhash'] == config.dev.genesis_prev_headerhash: return logger.warning('%s genesis_prev_headerhash mismatch', self.identity) logger.warning('Expected: ', config.dev.genesis_prev_headerhash) logger.warning('Found: ', data['genesis_prev_headerhash']) except Exception as e: logger.error('Peer Caused Exception %s', self.identity) logger.exception(e) self.transport.loseConnection() return
def broadcast(self, msg_hash, msg_type): # Move to factory data = {'hash': sha256(str(msg_hash)), 'type': msg_type} for peer in self.factory.peer_connections: if peer not in self.factory.master_mr.hash_peer[data['hash']]: peer.transport.write( self.wrap_message('MR', helper.json_encode(data)))
def get_peers(self): logger.info('<<<Sending connected peers to %s', self.transport.getPeer().host) peers_list = [] for peer in self.factory.peer_connections: peers_list.append(peer.transport.getPeer().host) self.transport.write( self.wrap_message('PL', helper.json_encode(peers_list))) return
def register_and_broadcast(self, msg_type, msg_hash, msg_json): # FIXME: Try to keep parameters in the same order (consistency) self.master_mr.register(msg_hash, msg_json, msg_type) msg_hash = sha256(str(msg_hash)) data = {'hash': msg_hash, 'type': msg_type} for peer in self.peer_connections: if msg_hash in self.master_mr.hash_peer: if peer in self.master_mr.hash_peer[msg_hash]: continue peer.transport.write(self.f_wrap_message('MR', json_encode(data)))
def send_m_blockheight_to_peer(self): z = { 'headerhash': self.factory.chain.m_blockchain[-1].blockheader.headerhash, 'block_number': 0 } if len(self.factory.chain.m_blockchain): z['block_number'] = self.factory.chain.m_blockchain[ -1].blockheader.blocknumber self.transport.write(self.wrap_message('CB', helper.json_encode(z))) return
def FMBH(self): # Fetch Maximum Blockheight and Headerhash if self.factory.pos.nodeState.state != NState.synced: return logger.info('<<<Sending blockheight and headerhash to: %s %s', self.transport.getPeer().host, str(time.time())) data = { 'headerhash': self.factory.chain.m_blockchain[-1].blockheader.headerhash, 'blocknumber': self.factory.chain.m_blockchain[-1].blockheader.blocknumber } self.transport.write( self.wrap_message('PMBH', helper.json_encode(data)))
def send_tx(self, wallet_from, wallet_to, amount_arg): fee_arg = 0 # FIXME: Fee argument is missing here, the class will be removed. No plans for fixing this self.txnResult = { 'status': 'fail', 'message': '', 'txnhash': '', 'from': wallet_from, 'to': wallet_to, 'amount': amount_arg } qrlnode = self.qrlnode ######################## ######################## try: wallet_from = qrlnode.get_wallet_absolute(wallet_from) wallet_to = qrlnode.get_wallet_absolute(wallet_to) amount = qrlnode.get_dec_amount(amount_arg) fee = qrlnode.get_dec_amount(fee_arg) tx = qrlnode.transfer_coins(wallet_from, wallet_to, amount, fee) except Exception as e: self.txnResult["message"] = str(e) return bytes(helper.json_encode(self.txnResult), 'utf-8') ################################ ################################ self.txnResult["status"] = "success" self.txnResult["txnhash"] = str(tx.txhash) self.txnResult["from"] = str(tx.txfrom) self.txnResult["to"] = str(tx.txto) # FIXME: Magic number? Unify self.txnResult["amount"] = str(tx.amount / 100000000.000000000) return bytes(helper.json_encode(self.txnResult), 'utf-8')
def send_m_blockheight_to_peer(self): """ Send mainchain blockheight to peer Sends the mainchain maximum blockheight request. :return: """ z = {'headerhash': self.factory.chain.m_blockchain[-1].blockheader.headerhash, 'block_number': 0} if len(self.factory.chain.m_blockchain): z['block_number'] = self.factory.chain.m_blockchain[-1].blockheader.blocknumber self.transport.write(self.wrap_message('CB', helper.json_encode(z))) return
def FMBH(self): # Fetch Maximum Blockheight and Headerhash """ Fetch Maximum Blockheight and HeaderHash Serves the fetch request for maximum blockheight & headerhash Sends the current blockheight and the headerhash of the last block in mainchain. :return: """ if self.factory.pos.nodeState.state != NState.synced: return logger.info('<<<Sending blockheight and headerhash to: %s %s', self.transport.getPeer().host, str(time.time())) data = {'headerhash': self.factory.chain.m_blockchain[-1].blockheader.headerhash, 'blocknumber': self.factory.chain.m_blockchain[-1].blockheader.blocknumber} self.transport.write(self.wrap_message('PMBH', helper.json_encode(data))) return
def broadcast(self, msg_hash, msg_type, data=None): # Move to factory """ Broadcast This function sends the Message Receipt to all connected peers. :return: """ msg_hash_str = bin2hstr(tuple(msg_hash)) ignore_peers = [] if msg_hash_str in self.master_mr.requested_hash: ignore_peers = self.master_mr.requested_hash[msg_hash_str].peers_connection_list if not data: data = {'hash': sha256(msg_hash), 'type': msg_type} for peer in self.peer_connections: if peer in ignore_peers: continue peer.transport.write(self.protocol.wrap_message('MR', json_encode(data)))
def RFM(self, data): """ Request Full Message This function request for the full message against, the Message Receipt received. :return: """ # FIXME: Again, breaking encasulation # FIXME: Huge amount of lookups in dictionaries msg_hash = data['hash'] msg_hash_str = bin2hstr(msg_hash) if msg_hash_str in self.master_mr.hash_msg: if msg_hash_str in self.master_mr.requested_hash: del self.master_mr.requested_hash[msg_hash_str] return peers_list = self.master_mr.requested_hash[ msg_hash_str].peers_connection_list message_request = self.master_mr.requested_hash[msg_hash_str] for peer in peers_list: if peer in message_request.already_requested_peers: continue message_request.already_requested_peers.append(peer) peer.transport.write( peer.wrap_message('SFM', helper.json_encode(data))) call_later_obj = reactor.callLater( config.dev.message_receipt_timeout, self.RFM, data) message_request.callLater = call_later_obj return # If execution reach to this line, then it means no peer was able to provide # Full message for this hash thus the hash has to be deleted. # Moreover, negative points could be added to the peers, for this behavior if msg_hash_str in self.master_mr.requested_hash: del self.master_mr.requested_hash[msg_hash_str]
def RFM(self, data): # Request full message, Move to factory msg_hash = data['hash'] if msg_hash in self.factory.master_mr.hash_msg: if msg_hash in self.factory.master_mr.hash_callLater: del self.factory.master_mr.hash_callLater[msg_hash] return for peer in self.factory.master_mr.hash_peer[msg_hash]: if peer not in self.factory.master_mr.requested_hash[msg_hash]: self.factory.master_mr.requested_hash[msg_hash].append(peer) peer.transport.write( self.wrap_message('SFM', helper.json_encode(data))) call_later_obj = reactor.callLater( config.dev.message_receipt_timeout, self.RFM, data) self.factory.master_mr.hash_callLater[ msg_hash] = call_later_obj return # If executing reach to this line, then it means no peer was able to provide # Full message for this hash thus the hash has to be deleted. # Moreover, negative points could be added to the peers, for this behavior if msg_hash in self.factory.master_mr.hash_callLater: del self.factory.master_mr.hash_callLater[msg_hash]
def send_last_stake_reveal_one(self): for peer in self.peer_connections: peer.transport.write( self.protocol.wrap_message('R1', json_encode(self.last_reveal_one)))
def send_stake_reveal_one(self, blocknumber=None): if blocknumber is None: blocknumber = self.chain.block_chain_buffer.height( ) + 1 # next block.. reveal_msg = { 'stake_address': self.chain.mining_address, 'block_number': blocknumber, # demonstrate the hash from last block to prevent building upon invalid block.. 'headerhash': self.chain.block_chain_buffer.get_strongest_headerhash( blocknumber - 1) } epoch = blocknumber // config.dev.blocks_per_epoch hash_chain = self.chain.block_chain_buffer.hash_chain_get(blocknumber) # +1 to skip first reveal reveal_msg['reveal_one'] = hash_chain[-1][:-1][::-1][ blocknumber - (epoch * config.dev.blocks_per_epoch) + 1] reveal_msg['vote_hash'] = None reveal_msg['weighted_hash'] = None epoch_seed = self.chain.block_chain_buffer.get_epoch_seed(blocknumber) reveal_msg['seed'] = epoch_seed reveal_msg['SV_hash'] = self.chain.get_stake_validators_hash() stake_validators_list = self.chain.block_chain_buffer.get_stake_validators_list( blocknumber) target_chain = stake_validators_list.select_target( reveal_msg['headerhash']) hashes = hash_chain[target_chain] reveal_msg['vote_hash'] = hashes[:-1][::-1][ blocknumber - (epoch * config.dev.blocks_per_epoch)] if reveal_msg['reveal_one'] is None or reveal_msg['vote_hash'] is None: logger.info( 'reveal_one or vote_hash None for stake_address: %s selected hash: %s', reveal_msg['stake_address'], hash_chain[target_chain]) logger.info('reveal_one %s', reveal_msg['reveal_one']) logger.info('vote_hash %s', reveal_msg['vote_hash']) logger.info('hash %s', hash_chain[target_chain]) return reveal_msg['weighted_hash'] = self.chain.score( stake_address=reveal_msg['stake_address'], reveal_one=reveal_msg['reveal_one'], balance=self.chain.block_chain_buffer.get_st_balance( reveal_msg['stake_address'], blocknumber), seed=epoch_seed) y = False tmp_stake_reveal_one = [] for r in self.chain.stake_reveal_one: # need to check the reveal list for existence already, if so..reuse.. if r[0] == self.chain.mining_address: if r[1] == reveal_msg['headerhash']: if r[2] == blocknumber: if y: continue # if repetition then remove.. else: reveal_msg['reveal_one'] = r[3] y = True tmp_stake_reveal_one.append(r) self.chain.stake_reveal_one = tmp_stake_reveal_one logger.info( '<<<Transmitting POS reveal_one %s %s', blocknumber, self.chain.block_chain_buffer.get_st_balance( reveal_msg['stake_address'], blocknumber)) self.last_reveal_one = reveal_msg self.register_and_broadcast('R1', reveal_msg['vote_hash'], json_encode(reveal_msg)) if not y: self.chain.stake_reveal_one.append([ reveal_msg['stake_address'], reveal_msg['headerhash'], reveal_msg['block_number'], reveal_msg['reveal_one'], reveal_msg['weighted_hash'], reveal_msg['vote_hash'] ]) return reveal_msg['reveal_one']
def render_POST(self, request): req = request.content.read() jsQ = json.loads(req) self.result = { 'status': 'fail', 'message': '', 'recoveredAddress': '', 'hexseed': '', 'mnemonic': '' } # Recover address from mnemonic if jsQ["type"] == "mnemonic": # Fail if no words provided if not jsQ["words"]: self.result[ "message"] = "You must provide your mnemonic phrase!" return bytes(helper.json_encode(self.result), 'utf-8') # FIXME: Validation should not be here, it should be part of mnemonic mnemonicphrase = jsQ["words"] if not validate_mnemonic(mnemonicphrase): self.result[ "message"] = "Invalid mnemonic phrase! It must contain exactly 32 valid words" return bytes(helper.json_encode(self.result), 'utf-8') # Try to recover try: addr = self.chain.wallet.get_new_address( seed=mnemonic2bin(mnemonicphrase, wordlist)) self.chain.wallet.append_wallet(addr) # Find hex/mnemonic for recovered wallet self.result["recoveredAddress"] = addr[1].get_address() self.result["hexseed"] = addr[1].get_hexseed() self.result["mnemonic"] = addr[1].get_mnemonic() except: self.result[ "message"] = "There was a problem restoring your address. " \ "If you believe this is in error, please raise it with the QRL team." return bytes(helper.json_encode(self.result), 'utf-8') # Recover address from hexseed elif jsQ["type"] == "hexseed": if not jsQ["hexseed"] or not hexseed_to_seed(jsQ["hexseed"]): self.result["message"] = "Invalid Hex Seed!" return bytes(helper.json_encode(self.result), 'utf-8') # Try to recover try: addr = self.chain.wallet.get_new_address( seed=hexseed_to_seed(jsQ["hexseed"])) self.chain.wallet.append_wallet(addr) # Find hex/mnemonic for recovered wallet self.result["recoveredAddress"] = addr[1].get_address() self.result["hexseed"] = addr[1].get_hexseed() self.result["mnemonic"] = addr[1].get_mnemonic() except: self.result[ "message"] = "There was a problem restoring your address. If you believe this is in error, please raise it with the QRL team." return bytes(helper.json_encode(self.result), 'utf-8') # Invalid selection else: self.result[ "message"] = "You must select either mnemonic or hexseed recovery options to restore an address!" return bytes(helper.json_encode(self.result), 'utf-8') # If we got this far, it must have worked! self.result["status"] = "success" return bytes(helper.json_encode(self.result), 'utf-8')
def render_GET(self, request): return bytes( helper.json_encode( self.chain.wallet.list_addresses(self.chain.state, self.chain.transaction_pool)), 'utf-8')
def send_tx(self, wallet_from, wallet_to, send_amount): self.txnResult = { 'status': 'fail', 'message': '', 'txnhash': '', 'from': wallet_from, 'to': wallet_to, 'amount': send_amount } # Check if local wallet number is higher than the number of local wallets that are saved if int(wallet_from) > len( self.chain.wallet.list_addresses( self.chain.state, self.chain.transaction_pool)) - 1: self.txnResult[ "message"] = "Invalid sending address. Try a valid number from your wallet - type wallet for details." return bytes(helper.json_encode(self.txnResult), 'utf-8') # if wallet_to is not a local wallet, and wallet_to is not prepended by Q and if len(wallet_to) > 1 and wallet_to[0] != 'Q' and self.state.state_hrs( wallet_to) != False: pass elif wallet_to[0] == 'Q': pass else: try: int(wallet_to) except: self.txnResult[ "message"] = "Invalid receiving address - addresses must start with Q." return bytes(helper.json_encode(self.txnResult), 'utf-8') if int(wallet_to) > len( self.chain.wallet.list_addresses( self.chain.state, self.chain.transaction_pool)) - 1: self.txnResult[ "message"] = "Invalid receiving address - addresses must start with Q." return bytes(helper.json_encode(self.txnResult), 'utf-8') wallet_to = int(wallet_to) # Check to see if sending amount > amount owned (and reject if so) # This is hard to interpret. Break it up? balance = self.state.state_balance( self.chain.wallet.address_bundle[int(wallet_from)].address) try: float(send_amount) except: self.txnResult[ "message"] = "Invalid amount type. Type a number (less than or equal to the balance of the sending address)" return bytes(helper.json_encode(self.txnResult), 'utf-8') amount = decimal.Decimal(decimal.Decimal(send_amount) * 100000000).quantize( decimal.Decimal('1'), rounding=decimal.ROUND_HALF_UP) if balance < amount: self.txnResult[ "message"] = "Invalid amount to send. Type a number less than or equal to the balance of the sending address" return bytes(helper.json_encode(self.txnResult), 'utf-8') # Stop user from sending less than their entire balance if they've only # got one signature remaining. sigsremaining = self.chain.wallet.get_num_signatures( self.chain.wallet.address_bundle[int(wallet_from)].address) if sigsremaining is 1: if amount < balance: self.txnResult[ "message"] = "Stop! You only have one signing signature remaining. You should send your entire balance or the remainder will be lost!" return bytes(helper.json_encode(self.txnResult), 'utf-8') tx = self.chain.create_my_tx(txfrom=int(wallet_from), txto=bytes(wallet_to, 'utf-8'), amount=amount) if tx is False: self.txnResult["message"] = "Failed to Create txn" return bytes(helper.json_encode(self.txnResult), 'utf-8') if tx.validate_tx(): block_chain_buffer = self.chain.block_chain_buffer tx_state = block_chain_buffer.get_stxn_state( blocknumber=block_chain_buffer.height(), addr=tx.txfrom) if not tx.state_validate_tx( tx_state=tx_state, transaction_pool=self.chain.transaction_pool): self.txnResult["message"] = "OTS key reused" return bytes(helper.json_encode(self.txnResult), 'utf-8') else: self.txnResult["message"] = "TXN failed at validate_tx" return bytes(helper.json_encode(self.txnResult), 'utf-8') # send the transaction to peers (ie send it to the network - we are done) self.p2pFactory.send_tx_to_peers(tx) print(('>>> TXN Hash: ' + str(tx.txhash) + ', From: ' + str(tx.txfrom) + ' To: ' + str(tx.txto) + ' For: ' + str(tx.amount / 100000000.000000000) + '\r\n' + '>>>created and sent into p2p network')) self.txnResult["status"] = "success" self.txnResult["txnhash"] = str(tx.txhash) self.txnResult["from"] = str(tx.txfrom) self.txnResult["to"] = str(tx.txto) self.txnResult["amount"] = str(tx.amount / 100000000.000000000) return bytes(helper.json_encode(self.txnResult), 'utf-8')
def send_stake_reveal_one(self, blocknumber=None): z = { 'stake_address': self.chain.mining_address, 'block_number': blocknumber } if not z['block_number']: z['block_number'] = self.chain.block_chain_buffer.height( ) + 1 # next block.. z['headerhash'] = self.chain.block_chain_buffer.get_strongest_headerhash( z['block_number'] - 1 ) # demonstrate the hash from last block to prevent building upon invalid block.. epoch = z['block_number'] // config.dev.blocks_per_epoch hash_chain = self.chain.block_chain_buffer.hash_chain_get( z['block_number']) # +1 to skip first reveal z['reveal_one'] = hash_chain[-1][:-1][::-1][ z['block_number'] - (epoch * config.dev.blocks_per_epoch) + 1] z['vote_hash'] = None z['weighted_hash'] = None epoch_seed = self.chain.block_chain_buffer.get_epoch_seed(blocknumber) z['seed'] = epoch_seed z['SV_hash'] = self.chain.get_stake_validators_hash() _, mhash = self.chain.select_hashchain( last_block_headerhash=self.chain.block_chain_buffer. get_strongest_headerhash(z['block_number'] - 1), stake_address=self.chain.mining_address, blocknumber=z['block_number']) for hashes in hash_chain: if hashes[-1] == mhash: z['vote_hash'] = hashes[:-1][::-1][ z['block_number'] - (epoch * config.dev.blocks_per_epoch)] break if z['reveal_one'] is None or z['vote_hash'] is None: logger.info(('reveal_one or vote_hash None for stake_address: ', z['stake_address'], ' selected hash:', mhash)) logger.info('reveal_one %s', z['reveal_one']) logger.info('vote_hash %s', z['vote_hash']) logger.info('hash %s', mhash) return z['weighted_hash'] = self.chain.score( stake_address=z['stake_address'], reveal_one=z['reveal_one'], balance=self.chain.block_chain_buffer.get_st_balance( z['stake_address'], blocknumber), seed=epoch_seed) y = False tmp_stake_reveal_one = [] for r in self.chain.stake_reveal_one: # need to check the reveal list for existence already, if so..reuse.. if r[0] == self.chain.mining_address: if r[1] == z['headerhash']: if r[2] == z['block_number']: if y: continue # if repetition then remove.. else: z['reveal_one'] = r[3] y = True tmp_stake_reveal_one.append(r) self.chain.stake_reveal_one = tmp_stake_reveal_one logger.info( '<<<Transmitting POS reveal_one %s %s', blocknumber, self.chain.block_chain_buffer.get_st_balance( z['stake_address'], blocknumber)) self.last_reveal_one = z self.register_and_broadcast('R1', z['vote_hash'], json_encode(z)) # for peer in self.peers: # peer.transport.write(self.f_wrap_message('R1', helper.json_encode(z))) # score = self.chain.score(stake_address=self.chain.mining_address, # reveal_one=z['reveal_one'], # balance=self.chain.block_chain_buffer.get_st_balance(self.chain.mining_address, blocknumber), # seed=epoch_seed) if not y: self.chain.stake_reveal_one.append([ z['stake_address'], z['headerhash'], z['block_number'], z['reveal_one'], z['weighted_hash'], z['vote_hash'] ]) # don't forget to store our reveal in stake_reveal_one return z['reveal_one'] # , z['block_number']