Beispiel #1
0
    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)
Beispiel #2
0
    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
Beispiel #3
0
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)
Beispiel #4
0
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
Beispiel #5
0
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()
Beispiel #6
0
    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)
Beispiel #7
0
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)
Beispiel #8
0
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')
Beispiel #10
0
 def get(self, request):
     return Response(Block.get_active_block().depth + 1)