def is_block_chained(previous_block, next_block): follows_previous = next_block.previous_block == previous_block.id is_plus_one = next_block.height == previous_block.height + 1 previous_slot = slots.get_slot_number( previous_block.height, previous_block.timestamp ) next_slot = slots.get_slot_number(next_block.height, next_block.timestamp) is_after_previous_slot = previous_slot < next_slot return follows_previous and is_plus_one and is_after_previous_slot
async def post_block(self, data, ip): # TODO: Wrap everything in try except # TODO: Validate request data that it's correct block structure block_data = data["block"] # if not block_data: # raise Exception( # "There was no block in request to the /peer/blocks endpoint" # ) block = Block.from_dict(block_data) self.socket.log_info( "Received new block at height %s with %s transactions, from %s", block.height, block.number_of_transactions, ip, ) is_verified, errors = block.verify() if not is_verified: self.socket.log_error(errors) # TODO: raise Exception("Verification failed") last_block = self.db.get_last_block() if last_block.height >= block.height: self.socket.log_info( "Received block with height %s which was already processed. Our last " "block height %s. Skipping process queue.", block.height, last_block.height, ) return if self.process_queue.block_exists(block): self.socket.log_info( "Received block with height %s is already in process queue.", block.height, ) return current_slot = slots.get_slot_number(last_block.height, time.get_time()) received_slot = slots.get_slot_number(last_block.height, block.timestamp) if current_slot >= received_slot and is_block_chained( last_block, block): # Put the block to process queue self.process_queue.push_block(block) else: self.socket.log_info( "Discarded block %s because it takes a future slot", block.height)
async def get_status(self): last_block = self.db.get_last_block() return { "state": { "height": last_block.height if last_block else 0, "forgingAllowed": slots.is_forging_allowed(last_block.height, time.get_time()), "currentSlot": slots.get_slot_number(last_block.height, time.get_time()), "header": last_block.get_header() if last_block else {}, }, "config": { "version": get_chain_version(), "network": { "version": config.network["pubKeyHash"], "name": config.network["name"], "nethash": config.network["nethash"], "explorer": config.network["client"]["explorer"], "token": { "name": config.network["client"]["token"], "symbol": config.network["client"]["symbol"], }, }, }, }
def consume_queue(self): while True: serialized_block = self.process_queue.pop_block() if serialized_block: last_block = self.database.get_last_block() block = Block.from_serialized(serialized_block) status = self.process_block(block, last_block) logger.info(status) if status in [BLOCK_ACCEPTED, BLOCK_DISCARDED_BUT_CAN_BE_BROADCASTED]: # TODO: Broadcast only current block milestone = config.get_milestone(block.height) current_slot = slots.get_slot_number(block.height, time.get_time()) if current_slot * milestone["blocktime"] <= block.timestamp: # TODO: THIS IS MISSING logger.error("MISSING: IMPLEMENT BROADCASTING") else: # TODO: change this logger.info("Nothing to process. Sleeping for 1 sec") sleep(1) # Our chain can get out of sync when it doesn't receive all the blocks # to the p2p endpoint, so if we're not in sync, force sync to the last # block last_block = self.database.get_last_block() if not self.is_synced(last_block): logger.info("Force syncing with the network as we got out of sync") self.sync_chain() logger.info("Done force syncing")
def _validate_generator(self, block): delegates = self.database.get_active_delegates(block.height) if not delegates: logger.error("Could not find delegates for block height %s", block.height) return False slot_number = slots.get_slot_number(block.height, block.timestamp) generator_username = self.database.wallets.find_by_public_key( block.generator_public_key ).username forging_delegate = None forging_delegate = delegates[slot_number % len(delegates)] if ( forging_delegate and forging_delegate.public_key != block.generator_public_key ): forging_username = self.database.wallets.find_by_public_key( forging_delegate.public_key ).username logger.error( "Delegate %s (%s) not allowed to forge, should be %s (%s)", generator_username, block.generator_public_key, forging_username, forging_delegate.public_key, ) return False # TODO: this seems weird as we can't decide if delegate is allowed to forge, but # we still accept it as a valid generator if not forging_delegate: logger.info( "Could not decide if delegate %s (%s) is allowed to forge block %s", generator_username, block.generator_public_key, block.height, ) # TODO: This return is not in the official ark core implementation! return False logger.info( "Delegate %s (%s) allowed to forge block %s", generator_username, block.generator_public_key, block.height, ) return True
def verify(self): errors = [] # TODO: find a better way to get milestone data milestone = config.get_milestone(self.height) # Check that the previous block is set if it's not a genesis block if self.height > 1 and not self.previous_block: errors.append("Invalid previous block") # Chech that the block reward matches with the one specified in config if self.reward != milestone["reward"]: errors.append("Invalid block reward: {} expected: {}".format( self.reward, milestone["reward"])) # Verify block signature is_valid_signature = self.verify_signature() if not is_valid_signature: errors.append("Failed to verify block signature") # Check if version is correct on the block if self.version != milestone["block"]["version"]: errors.append("Invalid block version") # Check that the block timestamp is not in the future is_invalid_timestamp = slots.get_slot_number( self.height, self.timestamp) > slots.get_slot_number( self.height, time.get_time()) if is_invalid_timestamp: errors.append("Invalid block timestamp") # Check if all transactions are valid invalid_transactions = [ trans for trans in self.transactions if not trans.verify() ] if len(invalid_transactions) > 0: errors.append("One or more transactions are not verified") # Check that number of transactions and block.number_of_transactions match if len(self.transactions) != self.number_of_transactions: errors.append("Invalid number of transactions") # Check that number of transactions is not too high (except for genesis block) if (self.height > 1 and len(self.transactions) > milestone["block"]["maxTransactions"]): errors.append("Too many transactions") # Check if transactions add up to the block values applied_transactions = [] total_amount = 0 total_fee = 0 bytes_data = bytes() for transaction in self.transactions: if transaction.id in applied_transactions: errors.append("Encountered duplicate transaction: {}".format( transaction.id)) applied_transactions.append(transaction.id) total_amount += transaction.amount total_fee += transaction.fee bytes_data += unhexlify(transaction.id) if total_amount != self.total_amount: errors.append("Invalid total amount") if total_fee != self.total_fee: errors.append("Invalid total fee") if len(bytes_data) > milestone["block"]["maxPayload"]: errors.append("Payload is too large") if sha256(bytes_data).hexdigest() != self.payload_hash: errors.append("Invalid payload hash") return len(errors) == 0, errors