def test_timeout_over(self): # create a blocktree and add blocks to it b1 = Block(1, GENESIS.block_id, [Transaction(1, 'a', 1)], 1) b2 = Block(2, GENESIS.block_id, [Transaction(1, 'a', 2)], 2) b3 = Block(3, b2.block_id, [Transaction(1, 'a', 3)], 3) b4 = Block(4, b3.block_id, [Transaction(1, 'a', 4)], 4) b5 = Block(5, b2.block_id, [Transaction(1, 'a', 5)], 5) self.node.blocktree.add_block(b1) self.node.blocktree.add_block(b2) self.node.blocktree.add_block(b3) self.node.blocktree.add_block(b4) self.node.blocktree.add_block(b5) self.node.blocktree.head_block = b4 txn = Transaction(1, 'a', 1) self.node.new_txs = [txn] self.node.broadcast = MagicMock() self.node.state = 0 clock = task.Clock() self.node.reactor = clock self.node.timeout_over(txn) clock.advance(50) assert self.node.broadcast.called obj = self.node.broadcast.call_args[0][0] assert obj.last_committed_block == self.node.blocktree.committed_block.block_id
def test_valid_block(self): # create a blocktree and add blocks to it bt = Blocktree(0) bt.db = MagicMock() b1 = Block(1, GENESIS.block_id, [Transaction(0, 'c', 0)], 1) b2 = Block(2, GENESIS.block_id, [Transaction(0, 'c', 1)], 2) b3 = Block(3, b2.block_id, [Transaction(0, 'c', 2)], 3) b4 = Block(4, b3.block_id, [Transaction(0, 'c', 3)], 4) b5 = Block(5, b2.block_id, [Transaction(0, 'c', 4)], 5) bt.add_block(b1) bt.add_block(b2) bt.add_block(b3) bt.add_block(b4) bt.add_block(b5) bt.committed_block = b2 bt.head_block = b3 assert bt.valid_block(b4) bt.committed_block = b1 bt.head_block = b1 assert not bt.valid_block(b3) bt.committed_block = b3 bt.head_block = b4 assert not bt.valid_block(b3) assert not bt.valid_block(b2)
def test_block_set(self): """Test that __hash__ and __eq__ were implemented correctly.""" b1 = Block(0, GENESIS.block_id, ['a'], 1) block_set = set() block_set.add(b1) block_set.add(b1) assert len(block_set) == 1 b2 = Block(0, GENESIS.block_id, ['a'], 2) block_set.add(b2) assert len(block_set) == 2
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
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
def test_blk(self): """Test receipt of a Block. """ self.node.receive_block = MagicMock() txn1 = Transaction(0, 'command1', 1) txn2 = Transaction(0, 'command2', 2) block = Block(0, 0, [txn1, txn2], 1) s = block.serialize() self.proto.stringReceived(s) self.assertTrue(self.node.receive_block.called) obj = self.node.receive_block.call_args[0][0] self.assertEqual(type(obj), Block) self.assertEqual(obj.txs[0], txn1)
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)
def test_receive_paxos_message_try_ok_1(self): # try_ok message with no 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.broadcast = MagicMock() self.node.receive_paxos_message(try_ok, None) self.node.blocktree.nodes.update({b.block_id: b}) assert self.node.broadcast.called assert self.node.c_com_block == self.node.c_new_block
def test_common_ancestor(self): # create a blocktree and add blocks to it bt = Blocktree(0) bt.db = MagicMock() b1 = Block(1, GENESIS.block_id, [Transaction(0, 'c', 0)], 1) b2 = Block(2, GENESIS.block_id, [Transaction(0, 'c', 1)], 2) b3 = Block(3, b2.block_id, [Transaction(0, 'c', 2)], 3) b4 = Block(4, b3.block_id, [Transaction(0, 'c', 3)], 4) b5 = Block(5, b2.block_id, [Transaction(0, 'c', 4)], 5) bt.add_block(b1) bt.add_block(b2) bt.add_block(b3) bt.add_block(b4) bt.add_block(b5) assert bt.common_ancestor(b1, b2) == GENESIS assert bt.common_ancestor(b4, b5) == b2 assert bt.common_ancestor(b3, b4) == b3
def test_receive_request_blocks_message(self): b1 = Block(1, GENESIS.block_id, [Transaction(1, 'a', 1)], 1) b2 = Block(2, GENESIS.block_id, [Transaction(1, 'a', 2)], 2) b3 = Block(3, b2.block_id, [Transaction(1, 'a', 3)], 3) b4 = Block(4, b3.block_id, [Transaction(1, 'a', 4)], 4) b5 = Block(5, b2.block_id, [Transaction(1, 'a', 5)], 5) self.node.blocktree.add_block(b1) self.node.blocktree.add_block(b2) self.node.blocktree.add_block(b3) self.node.blocktree.add_block(b4) self.node.blocktree.add_block(b5) req = RequestBlockMessage(b4.block_id) self.node.respond = MagicMock() self.node.receive_request_blocks_message(req, None) assert self.node.respond.called
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
def test_rsp(self): """Test receipt of a RespondBlockMessage. """ self.node.receive_respond_blocks_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) rsb = RespondBlockMessage([block, block2]) s = rsb.serialize() self.proto.stringReceived(s) self.assertTrue(self.node.receive_respond_blocks_message.called) obj = self.node.receive_respond_blocks_message.call_args[0][0] self.assertEqual(type(obj), RespondBlockMessage) self.assertEqual(obj.blocks[0].txs[0], txn1)
def test_create_block(self): # create a blocktree and add blocks to it b1 = Block(1, GENESIS.block_id, [Transaction(1, 'a', 1)], 1) b2 = Block(2, GENESIS.block_id, [Transaction(1, 'a', 2)], 2) b3 = Block(3, b2.block_id, [Transaction(1, 'a', 3)], 3) b4 = Block(4, b3.block_id, [Transaction(1, 'a', 4)], 4) b5 = Block(5, b2.block_id, [Transaction(1, 'a', 5)], 5) self.node.blocktree.add_block(b1) self.node.blocktree.add_block(b2) self.node.blocktree.add_block(b3) self.node.blocktree.add_block(b4) self.node.blocktree.add_block(b5) self.node.blocktree.head_block = b4 self.node.new_txs = [Transaction(1, 'a', 6)] c = self.node.create_block() assert len(self.node.new_txs) == 0 assert self.node.blocktree.nodes.get(c.block_id) == c
def test_move_to_block(self): b1 = Block(1, GENESIS.block_id, [Transaction(1, 'a', 1)], 1) b2 = Block(2, GENESIS.block_id, [Transaction(2, 'a', 2)], 2) b3 = Block(3, b2.block_id, [Transaction(3, 'a', 3)], 3) b4 = Block(4, b3.block_id, [Transaction(4, 'a', 4)], 4) b5 = Block(5, b2.block_id, [Transaction(5, 'a', 5)], 5) b6 = Block(6, b4.block_id, [Transaction(6, 'a', 6)], 6) self.node.blocktree.add_block(b1) self.node.blocktree.add_block(b2) self.node.blocktree.add_block(b3) self.node.blocktree.add_block(b4) self.node.blocktree.add_block(b5) self.node.blocktree.add_block(b6) self.node.blocktree.head_block = b4 old = self.node.blocktree.head_block # should both have no effect self.node.move_to_block(b3) self.node.move_to_block(b4) assert self.node.blocktree.head_block == old self.node.move_to_block(b6) assert self.node.blocktree.head_block == b6 self.node.broadcast = MagicMock() self.node.move_to_block(b1) assert self.node.broadcast.called assert self.node.blocktree.head_block == b1
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)
def __init__(self, node_index): self.genesis = GENESIS self.head_block = GENESIS self.committed_block = GENESIS self.committed_blocks = [GENESIS.block_id] self.nodes = {} self.nodes.update({GENESIS.block_id: GENESIS}) self.counter = 0 self.ack_commits = {} # create a db instance (s.t blocks can be recovered after a crash) base_path = os.path.expanduser('~/.pichain') path = base_path + '/node_' + str(node_index) if not os.path.exists(path): os.makedirs(path) self.db = plyvel.DB(path, create_if_missing=True) # first load all the blocks for key, value in self.db: # block_id -> block if key.decode().isdigit(): block_id = int(key.decode()) block = Block.unserialize(value) self.nodes.update({block_id: block}) # load all block ids and counter for key, value in self.db: if key == b'committed_block': block = self.nodes.get(int(value.decode())) self.committed_block = block elif key == b'head_block': block = self.nodes.get(int(value.decode())) self.head_block = block elif key == b'counter': self.counter = int(value.decode()) elif key == b'genesis': block = self.nodes.get(int(value.decode())) self.genesis = block elif key == b'committed_blocks': block_ids = json.loads(value.decode()) self.committed_blocks = block_ids
def create_block(self): """Create a block containing `new_txs` and return it. Returns: Block: The block that was created. """ logger.debug('create a block') # store depth of current head_block (will be parent of new block) d = self.blocktree.head_block.depth # create block self.blocktree.counter += 1 if len(self.new_txs) < MAX_TXN_COUNT: b = Block(self.id, self.blocktree.head_block.block_id, self.new_txs, self.blocktree.counter) # create a new, empty list (do not use clear!) self.new_txs = [] else: logger.debug( 'Cannot fit all transactions in the block that is beeing created. Remaining transactions ' 'will be included in the next block.') txns_include = self.new_txs[:MAX_TXN_COUNT] b = Block(self.id, self.blocktree.head_block.block_id, txns_include, self.blocktree.counter) self.new_txs = self.new_txs[MAX_TXN_COUNT:] self.readjust_timeout() # compute its depth (will be fixed -> depth field is only set once) b.depth = d + len(b.txs) self.blocktree.db.put(b'counter', str(self.blocktree.counter).encode()) # add block to blocktree self.blocktree.add_block(b) # promote node if self.state != QUICK: self.state = max(QUICK, self.state - 1) logger.debug('Got promoted. State = %s', str(self.state)) # add state of creator node to block b.creator_state = self.state logger.debug('created block with block id = %s', str(b.block_id)) return b
def test_reach_genesis_block(self): b1 = Block(1, GENESIS.block_id, [Transaction(1, 'a', 1)], 1) b2 = Block(2, GENESIS.block_id, [Transaction(1, 'a', 2)], 2) b3 = Block(3, b2.block_id, [Transaction(1, 'a', 3)], 3) b4 = Block(4, b3.block_id, [Transaction(1, 'a', 4)], 4) b5 = Block(5, b2.block_id, [Transaction(1, 'a', 5)], 5) self.node.blocktree.add_block(b1) self.node.blocktree.add_block(b2) self.node.blocktree.add_block(b3) self.node.blocktree.add_block(b4) self.node.blocktree.add_block(b5) assert self.node.reach_genesis_block(b5) b = Block(1, 1234, [Transaction(1, 'a', 6)], 6) self.node.broadcast = MagicMock() self.node.reach_genesis_block(b) assert self.node.broadcast.called
from piChain.PaxosNetwork import ConnectionManager from piChain.blocktree import Blocktree from piChain.messages import PaxosMessage, Block, RequestBlockMessage, RespondBlockMessage, Transaction, \ AckCommitMessage from piChain.config import ACCUMULATION_TIME, MAX_COMMIT_TIME, MAX_TXN_COUNT, TESTING, RECOVERY_BLOCKS_COUNT # variables representing the state of a node QUICK = 0 MEDIUM = 1 SLOW = 2 EPSILON = 0.001 # genesis block GENESIS = Block(-1, None, [], 0) GENESIS.depth = 0 logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) if not TESTING: logging.disable(logging.DEBUG) class Node(ConnectionManager): """This class represents a piChain node. It is a subclass of the ConnectionManager class defined in the networking module. This allows to directly call functions like broadcast and respond from the networking module and to override the receive-methods which are called in the networking module based on the type of the message. Args: node_index (int): the index of this node into the peers dictionary. The entry defines its ip address and port.
def test_write(self): b1 = Block(1, GENESIS.block_id, [Transaction(0, 'c', 0)], 1) b2 = Block(2, GENESIS.block_id, [Transaction(0, 'c', 1)], 2) b3 = Block(3, b2.block_id, [Transaction(0, 'c', 2)], 3) b4 = Block(4, b3.block_id, [Transaction(0, 'c', 3)], 4) b5 = Block(5, b2.block_id, [Transaction(0, 'c', 4)], 5) self.bt.add_block(b1) self.bt.add_block(b2) self.bt.add_block(b3) self.bt.add_block(b4) self.bt.add_block(b5) assert self.bt.db.get(str(b1.block_id).encode()) == b1.serialize() assert self.bt.db.get(str(b2.block_id).encode()) == b2.serialize() assert self.bt.db.get(str(b3.block_id).encode()) == b3.serialize() assert self.bt.db.get(str(b4.block_id).encode()) == b4.serialize() assert self.bt.db.get(str(b5.block_id).encode()) == b5.serialize()
def test_read(self): b1 = Block(1, GENESIS.block_id, [Transaction(0, 'c', 0)], 1) b2 = Block(2, GENESIS.block_id, [Transaction(0, 'c', 1)], 2) b3 = Block(3, b2.block_id, [Transaction(0, 'c', 2)], 3) b4 = Block(4, b3.block_id, [Transaction(0, 'c', 3)], 4) b5 = Block(5, b2.block_id, [Transaction(0, 'c', 4)], 5) self.bt.add_block(b1) self.bt.add_block(b2) self.bt.add_block(b3) self.bt.add_block(b4) self.bt.add_block(b5) self.bt.db.close() # create another BLocktree wich should load the blocks stored by self.bt bt2 = Blocktree(0) assert bt2.db.get(str(b1.block_id).encode()) == b1.serialize() assert bt2.db.get(str(b2.block_id).encode()) == b2.serialize() assert bt2.db.get(str(b3.block_id).encode()) == b3.serialize() assert bt2.db.get(str(b4.block_id).encode()) == b4.serialize() assert bt2.db.get(str(b5.block_id).encode()) == b5.serialize() bt2.db.close()