def on_register(self): self.db = self.engine.db self.blockchain = self.engine.blockchain self.account = self.engine.account if not self.db.exists('node_id'): self.db.put('node_id', str(uuid.uuid4())) self.node_id = self.db.get('node_id') try: self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.s.settimeout(2) self.s.bind(('0.0.0.0', self.engine.config['port']['peers'])) self.s.listen(10) print("Started Peer Listen on 0.0.0.0:{}".format( self.engine.config['port']['peers'])) return True except Exception as e: tools.log("Could not start Peer Receive socket!") tools.log(e) sys.stderr.write(str(e) + '\n') return False
def listen(self): try: client_sock, address = self.s.accept() response, leftover = ntwrk.receive(client_sock) if response.getFlag(): message = Message.from_yaml(response.getData()) request = message.get_body() try: if hasattr(self, request['action']) \ and request['version'] == custom.version \ and message.get_header("node_id") != self.node_id: kwargs = copy.deepcopy(request) if request['action'] == 'greetings': kwargs['__remote_ip__'] = client_sock.getpeername() elif request['action'] == 'push_block': kwargs['node_id'] = message.get_header("node_id") del kwargs['action'] del kwargs['version'] result = getattr(self, request['action'])(**kwargs) else: result = 'Received action is not valid' except: result = 'Something went wrong while evaluating.\n' tools.log(sys.exc_info()) response = Message(headers={'ack': message.get_header('id'), 'node_id': self.node_id}, body=result) ntwrk.send(response, client_sock) client_sock.close() except Exception as e: import time time.sleep(0.1)
def on_register(self): # TODO: Add authentication support for redis try: redis_instance = redis.StrictRedis( host=os.environ.get('REDIS_URL', 'localhost'), db=self.engine.config['database']['index']) redis_instance.ping() self.DB = RedisStore(redis_instance) except Exception as e: tools.log(e) sys.stderr.write( 'Redis connection cannot be established!\nFalling to SQLAlchemy' ) return False try: self.salt = self.DB.get('salt').decode() if self.salt is None: raise Exception except Exception as e: self.salt = ''.join( random.choice(string.ascii_uppercase + string.digits) for _ in range(5)) self.DB.put('salt', self.salt.encode()) return True
def target(_candidate_block, queue): # Miner registered but no work is sent yet. import copy candidate_block = copy.deepcopy(_candidate_block) try: if candidate_block is None: return if 'nonce' in candidate_block: candidate_block.pop('nonce') halfHash = tools.det_hash(candidate_block) candidate_block['nonce'] = random.randint( 0, 10000000000000000000000000000000000000000) current_hash = tools.det_hash({ 'nonce': candidate_block['nonce'], 'halfHash': halfHash }) while current_hash > candidate_block['target']: candidate_block['nonce'] += 1 current_hash = tools.det_hash({ 'nonce': candidate_block['nonce'], 'halfHash': halfHash }) if current_hash <= candidate_block['target']: queue.put(candidate_block) except Exception as e: tools.log('miner f****d up' + str(e)) pass
def block_integrity_check(block): if not isinstance(block, dict): tools.log('Block is not a dict') return False if 'error' in block: tools.log('Errors in block') return False if not ('length' in block and isinstance(block['length'], int)): return False if 'version' not in block or block['version'] != custom.version: return False if 'target' not in block: tools.log('no target in block') return False if 'time' not in block: tools.log('no time') return False if block['time'] > time.time() + 60 * 6: tools.log('Received block is coming from future. Call the feds') return False return True
def rollback(self): if not self.simulating: tools.log('There isn\'t any ongoing simulation') return False self.log = dict() self.simulating = False return True
def insider(*args, **kwargs): while self.__threads[func.__name__]["running"]: try: func(*args, **kwargs) except Exception as e: tools.log('Exception occurred at thread {}\n{}'.format( func.__name__, traceback.format_exc())) return 0
def register(self): def service_target(service): service.set_state(Service.RUNNING) while service.get_state() == Service.RUNNING: try: order = service.into_service_queue.get(timeout=1) if isinstance(order, Order): result = Service.execute_order(service, order) self.service_responses[order.id] = result self.signals[order.id].set() service.into_service_queue.task_done() except TypeError: service.set_state(Service.STOPPED) self.service_responses[order.id] = True self.signals[order.id].set() except queue.Empty: pass def threaded_wrapper(func): def insider(*args, **kwargs): while self.__threads[func.__name__]["running"]: try: func(*args, **kwargs) except Exception as e: tools.log('Exception occurred at thread {}\n{}'.format( func.__name__, traceback.format_exc())) return 0 return insider cont = self.on_register() if not cont: tools.log("Service is not going to continue with registering!") return False # Start event loop self.event_thread = threading.Thread(target=service_target, args=(self, ), name=self.name) self.event_thread.start() # Start all side-threads for clsMember in self.__class__.__dict__.values(): if hasattr( clsMember, "decorator") and clsMember.decorator == threaded.__name__: new_thread = threading.Thread( target=threaded_wrapper(clsMember._original), args=(self, ), name=clsMember._original.__name__) self.__threads[clsMember._original.__name__] = { "running": True, "thread": new_thread } new_thread.start() return True
def __init__(self, engine): self.engine = engine self.DB = None self.blockchain = None try: db_location = os.path.join(self.engine.working_dir, 'client.db') DB = plyvel.DB(db_location, create_if_missing=True) self.DB = DB.prefixed_db(custom.version.encode()) except Exception as e: tools.log(e) sys.stderr.write('Database connection cannot be established!\n')
def set_state(self, state): # (INIT|RUNNING|STOPPED|TERMINATED) -> () """ Set the current state of the service. This should never be used outside of the service. Treat as private method. :param state: New state :return: None """ if state == Service.STOPPED or state == Service.TERMINATED: tools.log('{} got stopped'.format(self.__class__.__name__)) for thread_name in self.__threads.keys(): self.__threads[thread_name]["running"] = False self.__state = state
def commit(self): """ Commit simply erases the earlier snapshot. :return: """ if not self.simulating: tools.log('There isn\'t any ongoing simulation') return False for key, value in self.log.items(): self.DB.put(str(key).encode(), pickle.dumps(value)) self.log = dict() self.simulating = False return True
def on_register(self): print('Starting halocoin') if not self.db.register(): return False print("Firing up the Database") time.sleep(0.1) if not test_database(self.db): tools.log("Database service is not working.") return False b = self.db.get('init') if not b: print("Initializing records") self.db.put('init', True) self.db.put('length', -1) self.db.put('txs', []) self.db.put('peer_list', []) self.db.put('targets', {}) self.db.put('times', {}) self.db.put('mine', False) self.db.put('diffLength', '0') self.db.put('accounts', {}) self.db.put('known_length', -1) self.db.put('stop', False) if not self.account.register(): sys.stderr.write("Account service has failed. Exiting!\n") self.unregister_sub_services() return False if not self.blockchain.register(): sys.stderr.write("Blockchain service has failed. Exiting!\n") self.unregister_sub_services() return False if not self.peer_receive.register(): sys.stderr.write("Peer Receive service has failed. Exiting!\n") self.unregister_sub_services() return False if not self.peers_check.register(): sys.stderr.write("Peers Check service has failed. Exiting!\n") self.unregister_sub_services() return False api.run() return True
def set_default_wallet(self, wallet_name, password): try: from halocoin.model.wallet import Wallet encrypted_wallet_content = self.get_wallet(wallet_name) wallet = Wallet.from_string( tools.decrypt(password, encrypted_wallet_content)) if wallet.name == wallet_name: self.db.put('default_wallet', { "wallet_name": wallet_name, "password": password }) return True else: return False except Exception as e: tools.log(e) return False
def __init__(self, engine, dbname): self.engine = engine self.dbname = dbname self.DB = None self.iterator = None self.simulating = False self.simulation_owner = '' self.salt = None self.req_count = 0 self.log = dict() try: db_location = os.path.join(self.engine.working_dir, self.dbname) DB = plyvel.DB(db_location, create_if_missing=True) self.DB = DB.prefixed_db(custom.version.encode()) self.iterator = self.DB.iterator except Exception as e: tools.log(e) sys.stderr.write('Database connection cannot be established!\n')
def simulate(self): """ Database simulations are thread based batch transactions. When a simulation is started by a thread, any get or put operation is executed on the simulated database. Other threads :return: """ if self.simulating: tools.log('There is already an ongoing simulation! {}'.format( threading.current_thread().getName())) return False try: self.simulating = True self.simulation_owner = threading.current_thread().getName() return True except: return False
def worker(self): if not self.blockchain.tx_queue.empty() or not self.blockchain.blocks_queue.empty() or \ self.blockchain.get_chain_state() != BlockchainService.IDLE: time.sleep(0.1) return candidate_block = self.get_candidate_block() self.start_workers(candidate_block) possible_blocks = [] while self.threaded_running() and (self.db.get('length') + 1) == candidate_block['length']: api.miner_status() while not self.queue.empty(): possible_blocks.append(self.queue.get(timeout=0.01)) time.sleep(0.1) if len(possible_blocks) > 0: tools.log('Mined block') tools.log(possible_blocks[:1]) self.blockchain.blocks_queue.put( (possible_blocks[:1], 'miner')) break
def execute_order(service, order): """ Directly executes the order on service instance. Makes no thread checks, no synchronization attempts. :param service: Service instance :param order: Order object :return: result of the execution """ result = False if order.action == '__close_threaded__': result = True service.__threads[order.args[0]]["running"] = False elif order.action == '__shutdown_service__': result = True service.set_state(Service.STOPPED) elif hasattr(service, order.action): try: result = getattr(service, order.action)._original(service, *order.args, **order.kwargs) except: result = None tools.log(sys.exc_info()) return result
def execute(self, action, expect_result, args, kwargs): """ Execute an order that is triggered by annotated methods. This method should be treated as private. :param action: Action name :param expect_result: Whether to wait for result of action :param args: Argument list for method :param kwargs: Keyword argument list for method :return: result of action or None """ if self.get_state() != Service.RUNNING: return None result = None new_order = Order(action, args, kwargs) # This is already event thread and someone called a synced function. # We can run it now. if threading.current_thread().name == self.event_thread.name: result = Service.execute_order(self, new_order) return result self.signals[new_order.id] = threading.Event() self.into_service_queue.put(new_order) if expect_result: try: if self.signals[new_order.id].wait(): response = self.service_responses[new_order.id] del self.signals[new_order.id] del self.service_responses[new_order.id] result = response else: tools.log('Service wait timed out', self.__class__.__name__) except: tools.log(sys.exc_info()) pass return result
def tx_signature_check(tx): tx_copy = copy.deepcopy(tx) if 'signatures' not in tx or not isinstance(tx['signatures'], (list, )): tools.log('no signatures') return False if 'pubkeys' not in tx or not isinstance(tx['pubkeys'], (list, )): tools.log('no pubkeys') return False if len(tx['pubkeys']) == 0: tools.log('pubkey error') return False if len(tx['signatures']) > len(tx['pubkeys']): tools.log('there are more signatures than required') return False tx_copy.pop('signatures') msg = tools.det_hash(tx_copy) if not BlockchainService.sigs_match(copy.deepcopy(tx['signatures']), copy.deepcopy(tx['pubkeys']), msg): tools.log('sigs do not match') return False return True
def worker(self): if self.blockchain.get_chain_state( ) == blockchain.BlockchainService.SYNCING: time.sleep(0.1) return candidate_block = self.get_candidate_block() tx_pool = self.blockchain.tx_pool() self.start_workers(candidate_block) possible_blocks = [] while not MinerService.is_everyone_dead( self.pool) and self.threaded_running(): if self.db.get('length') + 1 != candidate_block[ 'length'] or self.blockchain.tx_pool() != tx_pool: candidate_block = self.get_candidate_block() tx_pool = self.blockchain.tx_pool() self.start_workers(candidate_block) try: while not self.queue.empty(): possible_blocks.append(self.queue.get(timeout=0.01)) except queue.Empty: pass if len(possible_blocks) > 0: break # This may seem weird. It is needed when workers finish so fast, while loop ends prematurely. try: while not self.queue.empty(): possible_blocks.append(self.queue.get(timeout=0.01)) except queue.Empty: pass if len(possible_blocks) > 0: tools.log('Mined block') tools.log(possible_blocks) self.blockchain.blocks_queue.put(possible_blocks)
def add_block(self, block): """Attempts adding a new block to the blockchain. Median is good for weeding out liars, so long as the liars don't have 51% hashpower. """ length = self.db.get('length') block_at_length = self.get_block(length) if int(block['length']) < int(length) + 1: return 1 elif int(block['length']) > int(length) + 1: return 2 tools.echo('add block: ' + str(block['length'])) if (length >= 0 and block['diffLength'] != tools.hex_sum(block_at_length['diffLength'], tools.hex_invert(block['target']))) \ or (length < 0 and block['diffLength'] != tools.hex_invert(block['target'])): tools.log(block['diffLength']) tools.log( tools.hex_sum(self.db.get('diffLength'), tools.hex_invert(block['target']))) tools.log(block['length']) tools.log('difflength is wrong') return 3 if length >= 0 and tools.det_hash( block_at_length) != block['prevHash']: tools.log('prevhash different') return 3 nonce_and_hash = tools.hash_without_nonce(block) if tools.det_hash(nonce_and_hash) > block['target']: tools.log('hash value does not match the target') return 3 if block['target'] != self.target(block['length']): tools.log('block: ' + str(block)) tools.log('target: ' + str(self.target(block['length']))) tools.log('wrong target') return 3 """ recent_time_values = self.recent_blockthings('times', custom.median_block_time_limit) median_block = tools.median(recent_time_values) if block['time'] < median_block: tools.log('Received block is generated earlier than median.') return 3 """ # Check that block includes exactly one mint transaction if 'txs' not in block: tools.log( 'Received block does not include txs. At least a coinbase tx must be present' ) return 3 # Sum of all mint type transactions must be one mint_present = sum( [0 if tx['type'] != 'mint' else 1 for tx in block['txs']]) if mint_present != 1: tools.log('Received block includes wrong amount of mint txs') return 3 for tx in block['txs']: if not BlockchainService.tx_integrity_check(tx).getFlag(): tools.log('Received block failed special txs check.') return 3 if not self.statedb.update_database_with_block(block): return 3 self.put_block(block['length'], block) self.db.put('length', block['length']) self.db.put('diffLength', block['diffLength']) orphans = self.tx_pool_pop_all() for orphan in sorted(orphans, key=lambda x: x['count'] if 'count' in x else -1): self.tx_queue.put(orphan) tools.techo('add block: ' + str(block['length'])) return 0
def add_block(self, block): """Attempts adding a new block to the blockchain. Median is good for weeding out liars, so long as the liars don't have 51% hashpower. """ length = self.db.get('length') if int(block['length']) < int(length) + 1: return 1 elif int(block['length']) > int(length) + 1: return 2 if (length >= 0 and block['diffLength'] != tools.hex_sum(self.db.get('diffLength'), tools.hex_invert(block['target']))) \ or (length < 0 and block['diffLength'] != tools.hex_invert(block['target'])): tools.log(block['diffLength']) tools.log( tools.hex_sum(self.db.get('diffLength'), tools.hex_invert(block['target']))) tools.log(block['length']) tools.log('difflength is wrong') return 3 if length >= 0 and tools.det_hash( self.db.get(length)) != block['prevHash']: tools.log('prevhash different') return 3 nonce_and_hash = tools.hash_without_nonce(block) if tools.det_hash(nonce_and_hash) > block['target']: tools.log('hash value does not match the target') return 3 if block['target'] != self.target(block['length']): tools.log('block: ' + str(block)) tools.log('target: ' + str(self.target(block['length']))) tools.log('wrong target') return 3 recent_time_values = self.recent_blockthings( 'times', custom.median_block_time_limit, self.db.get('length')) median_block = tools.median(recent_time_values) if block['time'] < median_block: tools.log('Received block is generated earlier than median.') return 3 if not self.account.update_accounts_with_block( block, add_flag=True, simulate=True): tools.log('Received block failed transactions check.') return 3 self.db.put(block['length'], block) self.db.put('length', block['length']) self.db.put('diffLength', block['diffLength']) orphans = self.tx_pool_pop_all() self.account.update_accounts_with_block(block, add_flag=True) for orphan in sorted(orphans, key=lambda x: x['count']): self.add_tx(orphan) return 0
def blockchain_process(self): """ In this thread we check blocks queue for possible additions to blockchain. Following type is expected to come out of the queue. Any other type will be rejected. ([candidate_blocks in order], peer_node_id) Only 3 services actually put stuff in queue: peer_listen, peer_check, miner PeerListen and PeerCheck obeys the expected style. Miner instead puts one block in candidate block list, node id is 'miner' :return: """ try: candidate = self.blocks_queue.get(timeout=0.1) self.set_chain_state(BlockchainService.SYNCING) try: if isinstance(candidate, tuple): blocks = candidate[0] node_id = candidate[1] total_number_of_blocks_added = 0 for block in blocks: if not BlockchainService.block_integrity_check( block) and node_id != 'miner': self.peer_reported_false_blocks(node_id) return self.db.simulate() try: length = self.db.get('length') for i in range(20): block = self.get_block(length) if self.fork_check(blocks, length, block): self.delete_block() length -= 1 else: break for block in blocks: add_block_result = self.add_block(block) if add_block_result == 2: # A block that is ahead of us could not be added. No need to proceed. break elif add_block_result == 0: total_number_of_blocks_added += 1 api.new_block() except Exception as e: print("halo") if total_number_of_blocks_added == 0 or self.db.get( 'length') != blocks[-1]['length']: # All received blocks failed. Punish the peer by lowering rank. self.db.rollback() if node_id != 'miner': self.peer_reported_false_blocks(node_id) else: self.db.commit() except Exception as e: tools.log(e) self.blocks_queue.task_done() except: pass try: candidate_tx = self.tx_queue.get(timeout=0.1) result = self.add_tx(candidate_tx) self.tx_queue.task_done() except: pass self.set_chain_state(BlockchainService.IDLE)