def nodes_block_submit(self, block_send): for k, v in self.peer_dict.items(): peer_ip = k # app_log.info(HOST) peer_port = int(v) # app_log.info(PORT) # connect to all nodes try: s_peer = socks.socksocket() s_peer.settimeout(0.3) if self.tor_conf == 1: s_peer.setproxy(socks.PROXY_TYPE_SOCKS5, "127.0.0.1", 9050) s_peer.connect( (peer_ip, int(peer_port))) # connect to node in peerlist print("Connected") print("Miner: Proceeding to submit mined block to node") connections.send(s_peer, "block", 10) connections.send(s_peer, block_send, 10) print("Miner: Block submitted to node {}".format(peer_ip)) except Exception as e: print("Miner: Could not submit block to node {} because {}". format(peer_ip, e)) pass
def api_listreceived(self, socket_handler, ledger_db, peers): """ Returns the total amount received for each given address with minconf, including empty addresses or not. :param socket_handler: :param ledger_db: :param peers: :return: """ received = {} # TODO: this is temporary. # Will need more work to send full featured info needed for https://bitcoin.org/en/developer-reference#listreceivedbyaddress # (confirmations and tx list) try: # get the addresses (it's a list, even if a single address) addresses = connections.receive(socket_handler) minconf = connections.receive(socket_handler) if minconf < 1: minconf = 1 include_empty = connections.receive(socket_handler) for address in addresses: temp = self._get_received(ledger_db, address, minconf) if include_empty or temp > 0: received[address] = temp print('api_listreceived', addresses, minconf, ':', received) connections.send(socket_handler, received) except Exception as e: raise
def api_listbalance(self, socket_handler, ledger_db, peers): """ Returns the total amount received for each given address with minconf, including empty addresses or not. :param socket_handler: :param ledger_db: :param peers: :return: """ balances = {} try: # get the addresses (it's a list, even if a single address) addresses = connections.receive(socket_handler) minconf = connections.receive(socket_handler) if minconf < 1: minconf = 1 include_empty = connections.receive(socket_handler) # TODO: Better to use a single sql query with all addresses listed? for address in addresses: temp = self._get_balance(ledger_db, address, minconf) if include_empty or temp > 0: balances[address] = temp print('api_listbalance', addresses, minconf, ':', balances) connections.send(socket_handler, balances) except Exception as e: raise
def calculate_difficulty(self): # calculate difficulty s_node = socks.socksocket() if self.tor_conf == 1: s_node.setproxy(socks.PROXY_TYPE_SOCKS5, "127.0.0.1", 9050) s_node.connect( (self.node_ip_conf, int(self.port))) # connect to local node connections.send(s_node, "blocklast", 10) blocklast = connections.receive(s_node, 10) db_block_hash = blocklast[7] connections.send(s_node, "diffget", 10) diff = connections.receive(s_node, 10) s_node.close() diff = int(diff[1]) diff_real = int(diff) if self.pool_conf == 0: diff = int(diff) else: # if pooled diff_pool = diff_real diff = self.percentage(self.pool_diff_percentage, diff_real) if diff > diff_pool: diff = diff_pool mining_condition = bin_convert(db_block_hash)[0:diff] return db_block_hash, diff, diff_real, mining_condition
def api_ping(self, socket_handler, ledger_db, peers): """ Void, just to allow the client to keep the socket open (avoids timeout) :param socket_handler: :param ledger_db: :param peers: :return: 'api_pong' """ connections.send(socket_handler, 'api_pong')
def api_gettransaction(self, socket_handler, ledger_db, peers): """ returns total balance for a list of addresses and minconf BEWARE: this is NOT the json rpc getbalance (that get balance for an account, not an address) :param socket_handler: :param ledger_db: :param peers: :return: """ transaction = {} try: # get the txid transaction_id = connections.receive(socket_handler) # and format format = connections.receive(socket_handler) # raw tx details dbhandler.execute_param( self.app_log, ledger_db, "SELECT * FROM transactions WHERE signature like ?", (transaction_id + '%', )) raw = ledger_db.fetchone() if not format: connections.send(socket_handler, raw) print('api_gettransaction', format, raw) return # current block height, needed for confirmations # dbhandler.execute(self.app_log, ledger_db, "SELECT MAX(block_height) FROM transactions") block_height = ledger_db.fetchone()[0] transaction['txid'] = transaction_id transaction['time'] = raw[1] transaction['hash'] = raw[5] transaction['address'] = raw[2] transaction['recipient'] = raw[3] transaction['amount'] = raw[4] transaction['fee'] = raw[8] transaction['reward'] = raw[9] transaction['keep'] = raw[10] transaction['openfield'] = raw[11] transaction['pubkey'] = base64.b64decode(raw[6]).decode('utf-8') transaction['blockhash'] = raw[7] transaction['blockheight'] = raw[0] transaction['confirmations'] = block_height - raw[0] # Get more info on the block the tx is in. dbhandler.execute_param( self.app_log, ledger_db, "SELECT timestamp, recipient FROM transactions WHERE block_height= ? AND reward > 0", (raw[0], )) block_data = ledger_db.fetchone() transaction['blocktime'] = block_data[0] transaction['blockminer'] = block_data[1] print('api_gettransaction', format, transaction) connections.send(socket_handler, transaction) except Exception as e: raise
def api_clearmempool(self, socket_handler, ledger_db, peers): """ Empty the current mempool :param socket_handler: :param ledger_db: :param peers: :return: 'ok' """ mp.MEMPOOL.clear() connections.send(socket_handler, 'ok')
def api_mempool(self, socket_handler, ledger_db, peers): """ Returns all the TX from mempool :param socket_handler: :param ledger_db: :param peers: :return: list of mempool tx """ txs = mp.MEMPOOL.fetchall(mp.SQL_SELECT_TX_TO_SEND) connections.send(socket_handler, txs)
def api_getblocksincewhere(self, socket_handler, ledger_db, peers): """ Returns the full transactions following a given block_height and with specific conditions Returns at most transactions from 720 blocks at a time (the most *older* ones if it truncates) so about 12 hours worth of data. Maybe huge, use with caution and restrictive queries only. :param socket_handler: :param ledger_db: :param peers: :return: """ info = [] # get the last known block since_height = connections.receive(socket_handler) where_conditions = connections.receive(socket_handler) print('api_getblocksincewhere', since_height, where_conditions) # TODO: feed as array to have a real control and avoid sql injection !important # Do *NOT* use in production until it's done. raise ValueError("Unsafe, do not use yet") """ [ ['','openfield','like','egg%'] ] [ ['', '('], ['','reward','>','0'] ['and','recipient','in',['','','']] ['', ')'], ] """ where_assembled = where_conditions conditions_assembled = () try: try: dbhandler.execute( self.app_log, ledger_db, "SELECT MAX(block_height) FROM transactions") # what is the max block height to consider ? block_height = min(ledger_db.fetchone()[0], since_height + 720) #print("block_height",block_height) dbhandler.execute_param(self.app_log, ledger_db, ( 'SELECT * FROM transactions WHERE block_height > ? and block_height <= ? and ( ' + where_assembled + ')'), (since_height, block_height) + conditions_assembled) info = ledger_db.fetchall() # it's a list of tuples, send as is. #print(all) except Exception as e: print(e) raise # print("info", info) connections.send(socket_handler, info) except Exception as e: print(e) raise
def connect_to_pool(self): s_pool = socks.socksocket() s_pool.settimeout(0.3) if self.tor_conf == 1: s_pool.setproxy(socks.PROXY_TYPE_SOCKS5, "127.0.0.1", 9050) s_pool.connect((self.pool_ip_conf, 8525)) # connect to pool print("Connected") print( "Miner: Asking pool for share qualification difficulty requirement" ) connections.send(s_pool, "diffp", 10) self.pool_diff_percentage = int(connections.receive(s_pool, 10)) print( "Miner: Received pool for share qualification difficulty requirement: {}%" .format(self.pool_diff_percentage)) s_pool.close() return self
def api_getblockswhereoflike(self, socket_handler, ledger_db, peers): """ Returns the full transactions following a given block_height and with openfield begining by the given string Returns at most transactions from 1440 blocks at a time (the most *older* ones if it truncates) so about 1 day worth of data. Maybe huge, use with caution and on restrictive queries only. :param socket_handler: :param ledger_db: :param peers: :return: """ info = [] # get the last known block since_height = int(connections.receive(socket_handler)) where_openfield_like = connections.receive(socket_handler) + '%' #print('api_getblockswhereoflike', since_height, where_openfield_like) try: try: dbhandler.execute( self.app_log, ledger_db, "SELECT MAX(block_height) FROM transactions") # what is the max block height to consider ? block_height = min(ledger_db.fetchone()[0], since_height + 1440) #print("block_height", since_height, block_height) dbhandler.execute_param( self.app_log, ledger_db, 'SELECT * FROM transactions WHERE block_height > ? and block_height <= ? and openfield like ?', (since_height, block_height, where_openfield_like)) info = ledger_db.fetchall() # it's a list of tuples, send as is. #print("info", info) except Exception as e: print("error", e) raise # Add the last fetched block so the client will be able to fetch the next block info.append([block_height]) connections.send(socket_handler, info) except Exception as e: print(e) exc_type, exc_obj, exc_tb = sys.exc_info() fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1] print(exc_type, fname, exc_tb.tb_lineno) raise
def api_getaddressinfo(self, socket_handler, ledger_db, peers): """ Returns a dict with known: Did that address appear on a transaction? pubkey: The pubkey of the address if it signed a transaction, :param address: The bismuth address to examine :return: dict """ info = {'known': False, 'pubkey': ''} # get the address address = connections.receive(socket_handler) # print('api_getaddressinfo', address) try: # format check if not re.match('[abcdef0123456789]{56}', address): self.app_log.info("Bad address format <{}>".format(address)) connections.send(socket_handler, info) return try: dbhandler.execute_param(self.app_log, ledger_db, ( 'SELECT block_height FROM transactions WHERE address= ? or recipient= ? LIMIT 1;' ), (address, address)) ledger_db.fetchone()[0] # no exception? then we have at least one known tx info['known'] = True dbhandler.execute_param(self.app_log, ledger_db, ( 'SELECT public_key FROM transactions WHERE address= ? and reward = 0 LIMIT 1;' ), (address, )) try: info['pubkey'] = ledger_db.fetchone()[0] info['pubkey'] = base64.b64decode( info['pubkey']).decode('utf-8') except Exception as e: print(e) pass except Exception as e: pass # returns info # print("info", info) connections.send(socket_handler, info) except Exception as e: pass
def api_getpeerinfo(self, socket_handler, ledger_db, peers): """ Returns a list of connected peers See https://bitcoin.org/en/developer-reference#getpeerinfo To be adjusted :return: list(dict) """ print('api_getpeerinfo') # TODO: Get what we can from peers, more will come when connections and connection stats will be modular, too. try: info = [{ 'id': id, 'addr': ip, 'inbound': True } for id, ip in enumerate(peers.consensus)] # TODO: peers will keep track of extra info, like port, last time, block_height aso. # TODO: add outbound connection connections.send(socket_handler, info) except Exception as e: pass
def api_getreceived(self, socket_handler, ledger_db, peers): """ returns total received amount for a *list* of addresses and minconf :param socket_handler: :param ledger_db: :param peers: :return: """ received = 0 try: # get the addresses (it's a list, even if a single address) addresses = connections.receive(socket_handler) minconf = connections.receive(socket_handler) if minconf < 1: minconf = 1 # TODO: Better to use a single sql query with all addresses listed? for address in addresses: received += self._get_received(ledger_db, address, minconf) print('api_getreceived', addresses, minconf, ':', received) connections.send(socket_handler, received) except Exception as e: raise
def api_getbalance(self, socket_handler, ledger_db, peers): """ returns total balance for a list of addresses and minconf BEWARE: this is NOT the json rpc getbalance (that get balance for an account, not an address) :param socket_handler: :param ledger_db: :param peers: :return: """ balance = 0 try: # get the addresses (it's a list, even if a single address) addresses = connections.receive(socket_handler) minconf = connections.receive(socket_handler) if minconf < 1: minconf = 1 # TODO: Better to use a single sql query with all addresses listed? for address in addresses: balance += self._get_balance(ledger_db, address, minconf) #print('api_getbalance', addresses, minconf,':', balance) connections.send(socket_handler, balance) except Exception as e: raise
def api_getblocksince(self, socket_handler, ledger_db, peers): """ Returns the full blocks and transactions following a given block_height Returns at most transactions from 10 blocks (the most recent ones if it truncates) Used by the json-rpc server to poll and be notified of tx and new blocks. :param socket_handler: :param ledger_db: :param peers: :return: """ info = [] # get the last known block since_height = connections.receive(socket_handler) #print('api_getblocksince', since_height) try: try: dbhandler.execute( self.app_log, ledger_db, "SELECT MAX(block_height) FROM transactions") # what is the min block height to consider ? block_height = max(ledger_db.fetchone()[0] - 11, since_height) #print("block_height",block_height) dbhandler.execute_param( self.app_log, ledger_db, ('SELECT * FROM transactions WHERE block_height > ?;'), (block_height, )) info = ledger_db.fetchall() # it's a list of tuples, send as is. #print(all) except Exception as e: print(e) raise # print("info", info) connections.send(socket_handler, info) except Exception as e: print(e) raise
def mine(self, q, privatekey_readable, public_key_hashed, address): Random.atfork() rndfile = Random.new() tries = 0 key = RSA.importKey(privatekey_readable) if self.pool_conf == 1: #do not use pools public key to sign, signature will be invalid self_address = address address = self.pool_address self.connect_to_pool() while True: try: # block_hash = hashlib.sha224(str(block_send) + db_block_hash).hexdigest() db_block_hash, diff, diff_real, mining_condition = self.calculate_difficulty( ) while tries < self.diff_recalc_conf: start = time.time() nonce = hashlib.sha224(rndfile.read(16)).hexdigest()[:32] mining_hash = bin_convert( hashlib.sha224( (address + nonce + db_block_hash).encode("utf-8")).hexdigest()) end = time.time() if tries % 2500 == 0: #limit output try: cycles_per_second = 1 / (end - start) print( "Thread{} {} @ {:.2f} cycles/second, difficulty: {}({}), iteration: {}" .format(q, db_block_hash[:10], cycles_per_second, diff, diff_real, tries)) except: pass tries += 1 if mining_condition in mining_hash: tries = 0 print("Thread {} found a good block hash in {} cycles". format(q, tries)) # serialize txs block_send = [] del block_send[:] # empty removal_signature = [] del removal_signature[:] # empty s_node = socks.socksocket() if self.tor_conf == 1: s_node.setproxy(socks.PROXY_TYPE_SOCKS5, "127.0.0.1", 9050) s_node.connect( (self.node_ip_conf, int(self.port))) # connect to config.txt node connections.send(s_node, "mpget", 10) data = connections.receive(s_node, 10) s_node.close() if data != "[]": mempool = data for mpdata in mempool: transaction = (str(mpdata[0]), str(mpdata[1][:56]), str(mpdata[2][:56]), '%.8f' % float(mpdata[3]), str(mpdata[4]), str(mpdata[5]), str(mpdata[6]), str(mpdata[7]) ) # create tuple # print transaction block_send.append( transaction ) # append tuple to list for each run removal_signature.append( str(mpdata[4]) ) # for removal after successful mining # claim reward block_timestamp = '%.2f' % time.time() transaction_reward = ( str(block_timestamp), str(address[:56]), str(address[:56]), '%.8f' % float(0), "0", str(nonce)) # only this part is signed! # print transaction_reward h = SHA.new(str(transaction_reward).encode("utf-8")) signer = PKCS1_v1_5.new(key) signature = signer.sign(h) signature_enc = base64.b64encode(signature) if signer.verify(h, signature): print("Signature valid") block_send.append( (str(block_timestamp), str(address[:56]), str(address[:56]), '%.8f' % float(0), str(signature_enc.decode("utf-8")), str(public_key_hashed.decode("utf-8")), "0", str(nonce))) # mining reward tx print("Block to send: {}".format(block_send)) if not any( isinstance(el, list) for el in block_send ): # if it's not a list of lists (only the mining tx and no others) new_list = [] new_list.append(block_send) block_send = new_list # make it a list of lists # claim reward # include data tries = 0 # submit mined block to node if self.sync_conf == 1: self.check_uptodate(300) if self.pool_conf == 1: mining_condition = bin_convert( db_block_hash)[0:diff_real] if mining_condition in mining_hash: print( "Miner: Submitting block to all nodes, because it satisfies real difficulty too" ) self.nodes_block_submit(block_send) try: s_pool = socks.socksocket() s_pool.settimeout(0.3) if self.tor_conf == 1: s_pool.setproxy( socks.PROXY_TYPE_SOCKS5, "127.0.0.1", 9050) s_pool.connect((self.pool_ip_conf, 8525)) # connect to pool print("Connected") print( "Miner: Proceeding to submit mined block to pool" ) connections.send(s_pool, "block", 10) connections.send(s_pool, self_address, 10) connections.send(s_pool, block_send, 10) s_pool.close() print("Miner: Block submitted to pool") except Exception as e: print( "Miner: Could not submit block to pool" ) pass if self.pool_conf == 0: self.nodes_block_submit(block_send) else: print("Invalid signature") tries = 0 except Exception as e: print(e) time.sleep(0.1) if self.debug_conf == 1: raise else: pass