Beispiel #1
0
    def test_receive_paxos_message_try(self):
        # try message
        try_msg = PaxosMessage('TRY', 1)
        try_msg.last_committed_block = GENESIS.block_id

        b = Block(1, GENESIS.block_id, ['a'], 1)
        b.depth = 1
        try_msg.new_block = b.block_id

        self.node.respond = MagicMock()
        self.node.blocktree.nodes.update({b.block_id: b})

        self.node.receive_paxos_message(try_msg, 1)
        assert self.node.respond.called
        assert self.node.s_max_block_depth == b.depth
Beispiel #2
0
    def test_receive_paxos_message_propose(self):
        propose = PaxosMessage('PROPOSE', 1)

        b = Block(1, GENESIS.block_id, ['a'], 1)
        b.depth = 1

        propose.new_block = GENESIS.block_id
        propose.com_block = GENESIS.block_id

        self.node.respond = MagicMock()
        self.node.receive_paxos_message(propose, 1)

        assert self.node.respond.called
        assert self.node.s_prop_block.block_id == propose.com_block
        assert self.node.s_supp_block.block_id == propose.new_block
Beispiel #3
0
    def parse_msg(self, msg_type, msg, sender):

        if msg_type != 'PON':
            logger.debug('parse_msg called with msg_type = %s', msg_type)
        if msg_type == 'RQB':
            obj = RequestBlockMessage.unserialize(msg)
            self.receive_request_blocks_message(obj, sender)
        elif msg_type == 'TXN':
            obj = Transaction.unserialize(msg)
            self.receive_transaction(obj)
        elif msg_type == 'BLK':
            obj = Block.unserialize(msg)
            self.receive_block(obj)
        elif msg_type == 'RSB':
            obj = RespondBlockMessage.unserialize(msg)
            self.receive_respond_blocks_message(obj)
        elif msg_type == 'PAM':
            obj = PaxosMessage.unserialize(msg)
            self.receive_paxos_message(obj, sender)
        elif msg_type == 'PON':
            obj = PongMessage.unserialize(msg)
            self.receive_pong_message(obj, sender.peer_node_id)
        elif msg_type == 'ACM':
            obj = AckCommitMessage.unserialize(msg)
            self.receive_ack_commit_message(obj)
Beispiel #4
0
    def test_receive_paxos_message_propose_ack(self):
        propose_ack = PaxosMessage('PROPOSE_ACK', 1)

        b = Block(1, GENESIS.block_id, ['a'], 1)
        b.depth = 1

        propose_ack.com_block = b.block_id

        self.node.c_request_seq = 1
        self.node.c_votes = 5
        self.node.blocktree.nodes.update({b.block_id: b})

        self.node.broadcast = MagicMock()
        self.node.commit = MagicMock()
        self.node.receive_paxos_message(propose_ack, None)

        assert self.node.broadcast.called

        obj = self.node.broadcast.call_args[0][0]
        assert obj.com_block == propose_ack.com_block
Beispiel #5
0
    def test_receive_paxos_message_try_ok_3(self):
        # try_ok message with no prop/supp block stored locally and message does contain a propose block
        try_ok = PaxosMessage('TRY_OK', 1)

        b = Block(1, GENESIS.block_id, ['a'], 1)
        b.depth = 1

        try_ok.supp_block = b.block_id
        try_ok.prop_block = b.block_id

        self.node.c_request_seq = 1
        self.node.c_new_block = b
        self.node.c_votes = 5

        self.node.broadcast = MagicMock()
        self.node.blocktree.nodes.update({b.block_id: b})
        self.node.receive_paxos_message(try_ok, None)

        assert self.node.broadcast.called
        assert self.node.c_prop_block == b

        obj = self.node.broadcast.call_args[0][0]
        assert obj.com_block == b.block_id
Beispiel #6
0
    def start_commit_process(self):
        """Commit `self.current_committable_block`."""
        self.retry_commit_timeout_queued = False

        if self.c_current_committable_block.block_id in self.blocktree.committed_blocks:
            # this block has already been committed
            return

        #  if quick node then start a new instance of paxos
        if self.state == QUICK and not self.c_commit_running:
            logger.debug('start an new instance of paxos')
            self.c_commit_running = True
            self.c_votes = 0
            self.c_request_seq += 1
            self.c_supp_block = None
            self.c_prop_block = None

            # set commit_running to False if after expected time needed for commit process still equals True
            deferLater(self.reactor, 2 * self.expected_rtt + MAX_COMMIT_TIME,
                       self.commit_timeout, self.c_request_seq)

            if not self.c_quick_proposing:
                self.c_new_block = self.c_current_committable_block

                # create try message
                try_msg = PaxosMessage('TRY', self.c_request_seq)
                try_msg.last_committed_block = self.blocktree.committed_block.block_id
                try_msg.new_block = self.c_new_block.block_id
                self.broadcast(try_msg, 'TRY')
                self.receive_paxos_message(try_msg, None)
            else:
                logger.debug('quick proposing')
                # create propose message directly
                propose = PaxosMessage('PROPOSE', self.c_request_seq)
                propose.com_block = self.c_current_committable_block.block_id
                propose.new_block = GENESIS.block_id
                self.broadcast(propose, 'PROPOSE')
                self.receive_paxos_message(propose, None)

        elif self.state == QUICK and self.c_commit_running:
            # try to commit block later
            logger.debug('commit is already running, try to commit later')
            if not self.retry_commit_timeout_queued:
                self.retry_commit_timeout_queued = True
                deferLater(self.reactor,
                           2 * self.expected_rtt + MAX_COMMIT_TIME,
                           self.start_commit_process)
Beispiel #7
0
    def test_pam(self):
        """Test receipt of a PaxosMessage.
        """
        self.node.receive_paxos_message = MagicMock()

        txn1 = Transaction(0, 'command1', 1)
        txn2 = Transaction(0, 'command2', 2)
        block = Block(0, 0, [txn1, txn2], 1)

        txn3 = Transaction(0, 'command3', 3)
        block2 = Block(1, 1, [txn1, txn3], 2)

        pam = PaxosMessage('TRY', 2)
        pam.new_block = block.block_id
        pam.last_committed_block = block2.block_id

        s = pam.serialize()
        self.proto.stringReceived(s)

        self.assertTrue(self.node.receive_paxos_message.called)
        obj = self.node.receive_paxos_message.call_args[0][0]
        self.assertEqual(type(obj), PaxosMessage)
        self.assertEqual(obj.new_block, block.block_id)
        self.assertEqual(obj.last_committed_block, block2.block_id)
Beispiel #8
0
    def test_receive_paxos_message_try_ok_2(self):
        # try_ok message with prop/supp block stored locally and message does not contain a propose block
        try_ok = PaxosMessage('TRY_OK', 1)

        b = Block(1, GENESIS.block_id, ['a'], 1)
        b.depth = 1

        self.node.c_request_seq = 1
        self.node.c_new_block = b
        self.node.c_votes = 5
        self.node.c_supp_block = GENESIS
        self.node.c_prop_block = GENESIS

        self.node.broadcast = MagicMock()
        self.node.receive_paxos_message(try_ok, None)

        assert self.node.broadcast.called
        assert self.node.c_supp_block == GENESIS
Beispiel #9
0
    def receive_paxos_message(self, message, sender):
        """React on a received paxos `message`. This method implements the main functionality of the paxos algorithm.

        Args:
            message (PaxosMessage): Message received.
            sender (Connection): Connection instance of the sender (None if sender is this Node).
        """
        logger.debug('receive message type = %s', message.msg_type)
        if message.msg_type == 'TRY':
            # make sure last commited block of sender is also committed by this node
            if message.last_committed_block not in self.blocktree.committed_blocks:
                last_committed_block = self.get_block(
                    message.last_committed_block)
                if last_committed_block is None:
                    return
                self.commit(last_committed_block)

            # make sure that message.new_block is descendant of last committed block
            new_block = self.get_block(message.new_block)
            if new_block is None:
                return
            if not self.reach_genesis_block(new_block):
                # first need to request some missing blocks to be able to decide
                return

            if not self.blocktree.ancestor(self.blocktree.committed_block,
                                           new_block):
                # new_block is not a descendent of last committed block thus we reject it
                return

            if self.s_max_block_depth < new_block.depth:
                self.s_max_block_depth = new_block.depth

                # write changes to disk (add s_max_block_depth)
                self.blocktree.db.put(b's_max_block_depth',
                                      str(self.s_max_block_depth).encode())

                # create a TRY_OK message
                try_ok = PaxosMessage('TRY_OK', message.request_seq)
                if self.s_prop_block is not None:
                    try_ok.prop_block = self.s_prop_block.block_id
                if self.s_supp_block is not None:
                    try_ok.supp_block = self.s_supp_block.block_id
                if sender is not None:
                    self.respond(try_ok, sender)
                else:
                    self.receive_paxos_message(try_ok, None)

        elif message.msg_type == 'TRY_OK':
            # check if message is not outdated
            if message.request_seq != self.c_request_seq:
                # outdated message
                logger.debug('TRY_OK outdated')
                return

            # if TRY_OK message contains a propose block, we will support it if it is the first received
            # or if its support block is deeper than the one already stored.
            supp_block = self.get_block(message.supp_block)
            prop_block = self.get_block(message.prop_block)

            if supp_block and self.c_supp_block is None:
                self.c_supp_block = supp_block
                self.c_prop_block = prop_block
            elif supp_block and self.c_supp_block and self.c_supp_block < supp_block:
                self.c_supp_block = supp_block
                self.c_prop_block = prop_block

            self.c_votes += 1
            if self.c_votes > self.n / 2:

                # start new round
                self.c_votes = 0
                self.c_request_seq += 1

                # the compromise block will be the block we are going to propose in the end
                self.c_com_block = self.c_new_block

                # check if we need to support another block instead of the new block
                if self.c_prop_block:
                    self.c_com_block = self.c_prop_block

                # create PROPOSE message
                propose = PaxosMessage('PROPOSE', self.c_request_seq)
                propose.com_block = self.c_com_block.block_id
                propose.new_block = self.c_new_block.block_id

                self.broadcast(propose, 'PROPOSE')
                self.receive_paxos_message(propose, None)

        elif message.msg_type == 'PROPOSE':
            # if did not receive a try message with a deeper new block in mean time can store proposed block on server
            new_block = self.get_block(message.new_block)
            if new_block is None:
                return
            com_block = self.get_block(message.com_block)
            if com_block is None:
                return
            if new_block.depth == self.s_max_block_depth:
                self.s_prop_block = com_block
                self.s_supp_block = new_block

                # write changes to disk (add s_prop_block and s_supp_block)
                if self.s_prop_block is not None:
                    block_id_bytes = str(self.s_prop_block.block_id).encode()
                    self.blocktree.db.put(b's_prop_block', block_id_bytes)

                if self.s_supp_block is not None:
                    block_id_bytes = str(self.s_supp_block.block_id).encode()
                    self.blocktree.db.put(b's_supp_block', block_id_bytes)

                # create a PROPOSE_ACK message
                propose_ack = PaxosMessage('PROPOSE_ACK', message.request_seq)
                propose_ack.com_block = message.com_block

                if sender is not None:
                    self.respond(propose_ack, sender)
                else:
                    self.receive_paxos_message(propose_ack, None)

        elif message.msg_type == 'PROPOSE_ACK':
            # check if message is not outdated
            if message.request_seq != self.c_request_seq:
                # outdated message
                return

            self.c_votes += 1
            if self.c_votes > self.n / 2:
                # ignore further answers
                self.c_request_seq += 1

                # create commit message
                com_block = self.get_block(message.com_block)
                if com_block is None:
                    return
                commit = PaxosMessage('COMMIT', self.c_request_seq)
                commit.com_block = message.com_block
                self.broadcast(commit, 'COMMIT')
                self.commit(com_block)

                # allow new paxos instance
                self.c_commit_running = False
                self.c_quick_proposing = True

        elif message.msg_type == 'COMMIT':
            com_block = self.get_block(message.com_block)
            if com_block is None:
                return
            self.commit(com_block)