def post(self, request): block = request.data.get('block') node = normalize_node(request.data.get('node')) nodes = get_nodes() if node not in nodes: return Response(status=400) logger.debug(f'Processing block {block["id"]} from node {node}...') # Check if we have the previous block this block refers to try: Block.objects.get(id=block['previous_block']) except Block.DoesNotExist: logger.debug( f'We do not have block {block["previous_block"]}, syncing...' ) sync(node) return Response() # Set up the block serializer = BlockSerializer(data=block) serializer.is_valid(raise_exception=True) _block = serializer.validated_data.copy() if block.get('extra_data'): _block['extra_data'] = b64decode(block['extra_data']) block_obj = Block(**_block) # Set up the transactions transactions = [] for t in block['transactions']: assert t.pop('block') == block_obj.id t_serializer = RemoteBlockTransactionSerializer(data=t) t_serializer.is_valid(raise_exception=True) _t = t_serializer.validated_data.copy() if t.get('extra_data'): _t['extra_data'] = b64decode(t['extra_data']) transaction = Transaction(**_t) transactions.append(transaction) # Validate the block and transactions if validate_block(block_obj, transactions): block_obj.save(transactions) # Delete any matching unconfirmed transactions UnconfirmedTransaction.objects.filter( hash__in=(t.hash for t in transactions) ).delete() return Response() else: logger.debug(f'Rejecting block from {node}') return Response(status=400)
def validate(self, data): # Check for extra data extra_data = self.context['request'].data.get('extra_data') if not data.get('extra_data') and extra_data: data['extra_data'] = b64decode(extra_data) # Build the transaction tx = UnconfirmedTransaction(**data) # Verify the hash tx.hash = tx.calculate_hash() if tx.hash != data['hash']: raise serializers.ValidationError('Invalid hash') # Verify the signature if not verify(tx.hash, tx.from_account, tx.signature): raise serializers.ValidationError('Bad signature') data['transaction'] = tx # Validate the transaction active_block = Block.get_active_block() if not validate_transaction( active_block.get_balances(), tx, prev_block=active_block, ): raise serializers.ValidationError('Transaction was not accepted.') return data
def _sync_blocks(node, blocks): # Request full block information from the node blocks = list(blocks) logger.debug(f'Downloading block data for {len(blocks)} blocks...') block_data = get_blocks(node, blocks) # Process each block with db_transaction.atomic(): logger.debug('Processing block data...') for block in blocks: logger.debug(f'Processing block {block}...') data = block_data[block] # Set up the block serializer = BlockSerializer(data=data) serializer.is_valid(raise_exception=True) _block = serializer.validated_data.copy() if data.get('extra_data'): _block['extra_data'] = b64decode(data['extra_data']) block_obj = Block(**_block) # Set up the transactions transactions = [] for t in data['transactions']: assert t.pop('block') == block_obj.id t_serializer = RemoteBlockTransactionSerializer(data=t) t_serializer.is_valid(raise_exception=True) _t = t_serializer.validated_data.copy() if t.get('extra_data'): _t['extra_data'] = b64decode(t['extra_data']) transaction = Transaction(**_t) transactions.append(transaction) # Validate the block and transactions if validate_block(block_obj, transactions): block_obj.save(transactions) # Delete any matching unconfirmed transactions UnconfirmedTransaction.objects.filter( hash__in=(t.hash for t in transactions)).delete() else: raise ValueError('Block failed validation.') # Make sure there aren't any other blocks we need to sync logger.debug('Double checking we are synced...') return _sync(node)
def is_time_to_mine(): """ Returns whether or not there are either at least 10 unconfirmed transactions, or it has been at least 10 minutes since the last block was mined. """ active_block = Block.get_active_block() minutes_passed = (now() - active_block.time).total_seconds() / 60 if UnconfirmedTransaction.objects.count() >= 10 or minutes_passed > 10: return True return False
def check_for_block(): # Get the active block active_block = Block.get_active_block() # Calculate how many minutes it has been since that block minutes_passed = (now() - active_block.time).total_seconds() / 60 logger.debug(f'Minutes passed since last block: {minutes_passed}') # Mine a new block if it has been 10 minutes if minutes_passed >= 10: logger.info('10 minutes passed, mining new block...') mine_block()
def get(self, request): before = request.GET.get('before') if before: from_block = get_object_or_404(Block, id=before) else: from_block = Block.get_active_block() ids = [] for i in range(100): ids.append(from_block.id) if from_block.previous_block: from_block = from_block.previous_block else: break return Response(ids)
def mine_block(): logger.debug('Checking for sync locks...') if SyncLock.objects.count(): logger.debug("Looks like we're syncing, canceling mine_block call.") return logger.debug('Mining new block...') broadcast = None with transaction.atomic(): # Get the active block active_block = Block.get_active_block() logger.debug(f'{active_block.id} is the active block.') # Collect the unconfirmed transactions unconfirmed = UnconfirmedTransaction.objects.all() logger.debug(f'{len(unconfirmed)} unconfirmed transactions found.') # Prune invalid transactions transactions = prune_invalid_transactions(active_block, unconfirmed) logger.debug(f'{len(transactions)} transactions after pruning.') # Convert the unconfirmed transactions and add the block reward transactions = [u.to_transaction() for u in transactions] transactions.insert(0, Transaction.create_block_reward()) # Set up the block block = Block( previous_block=active_block, depth=active_block.depth + 1, miner=settings.MINER_PUBLIC_KEY, extra_data=settings.BLOCK_EXTRA_DATA, time=now(), ) block.set_merkle_root(transactions) block.set_balances( apply_transactions_to_balances( transactions, active_block.get_balances(), )) block.set_hash() block.sign() # Validate the block and transactions if validate_block(block, transactions): # Save the block block.save(transactions) UnconfirmedTransaction.objects.all().delete() broadcast = block logger.info(f'Block {block.id} successfully mined.') else: logger.info('Failed to mine block - validation error!') if broadcast: from boocoin.p2p import broadcast_block broadcast_block(broadcast)
def validate_block(block, transactions): logger.debug(f'Validating block {block.id}') # Verify the block hash if block.id != block.calculate_hash(): return binvalid('Hash is incorrect') # Get the previous block try: previous_block = Block.objects.get(id=block.previous_block_id) except Block.DoesNotExist: return binvalid('Previous block does not exist') # Verify the depth of the block if block.depth != previous_block.depth + 1: return binvalid('Depth is incorrect') # Ensure the block isn't in the future if block.time > now(): return binvalid('Time is in the future') # Verify the block has 11 transactions (10 + 1) or it has been 10 minutes minutes_passed = (block.time - previous_block.time).total_seconds() / 60 if len(transactions) < 11 and minutes_passed < 10: return binvalid('Transaction count and minutes passed are wrong') # A miner must be set if not block.miner: return binvalid('Missing miner') # The miner must be in the genesis block genesis_block = Block.get_genesis_block() miners = json.loads(genesis_block.extra_data.decode('utf-8')) if block.miner not in miners: return binvalid('Miner not in genesis block') # Verify the miner's signature valid_signature = verify( content=block.id, public_key=block.miner, signature=block.signature, ) if not valid_signature: return binvalid('Bad signature') # Verify merkle root expected_merkle_root = calculate_merkle_root(t.hash for t in transactions) if expected_merkle_root != block.merkle_root: return binvalid('Bad merkle root') # Verify balances balances = previous_block.get_balances() for idx, transaction in enumerate(transactions): if not validate_transaction( balances, transaction, prev_block=previous_block, first_in_block=(idx == 0) ): return binvalid('Invalid transaction detected') # All checks passed, the block is valid logger.debug('Block validated') return True
def handle(self, *args, **options): miners = options['miner_public_keys'] # Verify the user doesn't already have a genesis block if Block.objects.count() > 0: sys.stderr.write("You already have a genesis block!\n") sys.exit(1) # This block will be signed by the miner, so its key must be included if settings.MINER_PUBLIC_KEY not in miners: sys.stderr.write("Your public key must be included.\n") sys.exit(1) # Set up the first transaction transaction = Transaction.create_block_reward() transactions = [transaction] # Set up the genesis block block = Block( depth=0, miner=settings.MINER_PUBLIC_KEY, extra_data=json.dumps(miners).encode('utf-8'), time=now(), ) block.set_merkle_root(transactions) block.set_balances(apply_transaction_to_balances(transaction, {})) block.set_hash() block.sign() block.save(transactions) # Store the genesis block information in a file call_command('dumpdata', 'boocoin.Block', 'boocoin.Transaction', indent=4, output='genesis.json') self.stdout.write('Genesis block saved to db and genesis.json\n')
def get(self, request): return Response(Block.get_active_block().depth + 1)