def test_create_link_to_com_chain(self, monkeypatch): """ Test creating a linked half that points back towards a previous block """ key = default_eccrypto.generate_key(u"curve25519") com_key = default_eccrypto.generate_key(u"curve25519").pub().key_to_bin() db = MockDBManager() com_link = Links(((1, ShortKey("30303030")),)) link = FakeBlock(com_id=com_key, links=com_link) monkeypatch.setattr( MockDBManager, "get_chain", lambda _, chain_id: MockChain() if chain_id == com_key else None, ) monkeypatch.setattr( MockChain, "consistent_terminal", Links((link.com_dot,)), ) block = BamiBlock.create( b"test", encode_raw({"id": 42}), db, key.pub().key_to_bin(), com_id=com_key ) # include the personal community # Attach to the assert block.links == Links((link.com_dot,)) assert block.previous == Links((GENESIS_DOT,)) assert block.sequence_number == GENESIS_SEQ assert block.com_seq_num == link.com_seq_num + 1 assert block.public_key == key.pub().key_to_bin() assert block.signature == EMPTY_SIG assert block.type == b"test" assert block.transaction == encode_raw({"id": 42}) assert block.com_id == com_key
def test_risky_spend(self): self.chain_id = self.minter spend_value = Decimal(10.00, self.con) spend_dot = Dot((1, "11111")) self.state.apply_spend( self.chain_id, GENESIS_LINK, GENESIS_LINK, spend_dot, self.spender, self.receiver, spend_value, ) assert float(self.state.get_balance(self.spender)) == -10 # Next spend - tries to pretend as if hasn't happen self.new_spend_value = Decimal(6.00, self.con) self.new_spend_dot = Dot((2, "22222")) self.state.apply_spend( self.chain_id, Links((spend_dot,)), Links((spend_dot,)), self.new_spend_dot, self.spender, self.receiver, self.new_spend_value, ) # As the value is less -> the balance will not change until confirmed, or rejected assert float(self.state.get_balance(self.spender)) == -10
class StdVals: terminal = Links(((5, b"test1"), )) term_bits = (True, ) holes = Ranges(()) incon = Links(()) conflicts = {} con_terminal = terminal
def test_create_next_pers(self, monkeypatch): """ Test creating a block that points towards a previous block """ db = MockDBManager() prev = FakeBlock() monkeypatch.setattr( MockDBManager, "get_chain", lambda _, chain_id: MockChain(), ) monkeypatch.setattr( MockChain, "consistent_terminal", Links((prev.pers_dot, )), ) block = BamiBlock.create(b"test", encode_raw({b"id": 42}), db, prev.public_key) assert block.previous == Links((prev.pers_dot, )) assert block.sequence_number == prev.sequence_number + 1 assert block.public_key == prev.public_key assert block.signature == EMPTY_SIG assert block.type == b"test" assert block.transaction == encode_raw({b"id": 42}) assert block.com_id == EMPTY_PK assert block.com_seq_num == UNKNOWN_SEQ
class InconVals: terminal = Links(((0, b"gen"), (5, b"test1"))) term_bits = ( True, False, ) holes = Ranges(((1, 4), )) incon = Links(((4, b"test1"), )) con_terminal = Links(((0, b"gen"), )) conflicts = {(5, b"test1"): {1: (b"gen", ), 5: (b"test2", )}}
def test_spend_fork(self): value = Decimal(1, self.con) dot = Dot((1, b"123123")) chain_id = self.spender self.state.apply_spend( chain_id, GENESIS_LINK, GENESIS_LINK, dot, self.spender, self.receiver, value, ) assert float(self.state.get_balance(self.spender)) == -1 new_dot = Dot((1, b"5464646")) self.state.apply_spend( chain_id, GENESIS_LINK, GENESIS_LINK, new_dot, self.spender, self.receiver, value, ) assert float(self.state.get_balance(self.spender)) == -2 assert self.state.is_chain_forked(chain_id, self.spender) assert self.state.was_chain_forked(chain_id, self.spender) # Fix the fork: f_dot = Dot((2, b"000000")) value = Decimal(3, self.con) self.state.apply_spend( chain_id, Links((dot, new_dot)), Links((dot, new_dot)), f_dot, self.spender, self.receiver, value, ) assert float(self.state.get_balance(self.spender)) == -3 assert not self.state.is_chain_forked(chain_id, self.spender) assert self.state.was_chain_forked(chain_id, self.spender)
def create_block_batch(com_id, num_blocks=100, txs=None): blocks = [] last_block_point = GENESIS_LINK for k in range(num_blocks): tx = txs[k] if txs else None blk = FakeBlock(com_id=com_id, links=last_block_point, transaction=tx) blocks.append(blk) last_block_point = Links(((blk.com_seq_num, blk.short_hash), )) return blocks
def test_prev_iter(self, create_batches, insert_function, chain): batches = create_batches(num_batches=2, num_blocks=10) prev_links = Links((GENESIS_DOT, )) for i in range(len(batches[0])): wrap_return(insert_function(chain, [batches[0][i]])) assert len(chain.get_prev_links(chain.terminal[0])) == 1 assert chain.get_prev_links(chain.terminal[0]) == prev_links prev_links = chain.terminal
def test_compare_frontiers(): val = StdVals f = Frontier(val.terminal, val.holes, val.incon) f2 = copy(f) f2.terminal = Links(((6, b"test2"), )) assert f != f2 assert f2 > f inc_f = Frontier(InconVals.terminal, InconVals.holes, InconVals.incon) assert f > inc_f assert f2 > inc_f
def process_block_unordered(self, blk: BamiBlock, peer: Peer) -> None: self.unordered_notifier.notify(blk.com_prefix + blk.com_id, blk) if peer != self.my_peer: frontier = Frontier(Links((blk.com_dot,)), holes=(), inconsistencies=()) subcom_id = blk.com_prefix + blk.com_id processing_queue = self.incoming_frontier_queue(subcom_id) if not processing_queue: raise UnknownChainException( "Cannot process block received block with unknown chain. {subcom_id}".format( subcom_id=subcom_id ) ) processing_queue.put_nowait((peer, frontier, True))
def test_valid_spend_with_reject(self): chain_id, value, spend_dot = self.test_mint_and_spend() reject_dot = Dot((3, "33333")) # Add confirmation from the counter-party self.state.apply_reject( chain_id, self.receiver, Links((spend_dot,)), reject_dot, self.spender, spend_dot, ) # As the transaction is confirmed, inconsistency is resolved assert float(self.state.get_balance(self.spender)) == 3 + value assert float(self.state.get_balance(self.receiver)) == 0
def test_create_genesis(self): """ Test creating a genesis block """ key = default_eccrypto.generate_key(u"curve25519") db = MockDBManager() block = BamiBlock.create(b"test", encode_raw({b"id": 42}), db, key.pub().key_to_bin()) assert block.previous == Links((GENESIS_DOT, )) assert block.sequence_number == GENESIS_SEQ assert block.public_key == key.pub().key_to_bin() assert block.signature == EMPTY_SIG assert block.type == b"test" assert block.transaction == encode_raw({b"id": 42}) assert block.com_id == EMPTY_PK assert block.com_seq_num == UNKNOWN_SEQ
def test_risky_spend_with_reject(self): self.test_risky_spend() reject_dot = Dot((3, "33333")) # Add confirmation from the counter-party self.state.apply_reject( self.chain_id, self.receiver, Links((self.new_spend_dot,)), reject_dot, self.spender, self.new_spend_dot, ) # As the transaction is rejected - the effect of it is reverted to the previous stable state - zero. assert float(self.state.get_balance(self.spender)) == 0 assert float(self.state.get_balance(self.receiver)) == 0 assert self.state.was_balance_negative(self.spender)
def test_risky_spend_with_confirm(self): self.test_risky_spend() confirm_dot = Dot((3, "33333")) # Add confirmation from the counter-party self.state.apply_confirm( self.chain_id, self.receiver, Links((self.new_spend_dot,)), confirm_dot, self.spender, self.new_spend_dot, self.new_spend_value, ) # As the transaction is confirmed, inconsistency is resolved assert float(self.state.get_balance(self.spender)) == -6 assert float(self.state.get_balance(self.receiver)) == 6 assert self.state.was_balance_negative(self.spender)
def create_block_batch(com_id, num_blocks=100): """ Create a given amount of blocks. Each block points to the previous one. Args: com_id: The community identifier of the block batch. num_blocks: The number of blocks to create. Returns: A list with blocks. """ blocks = [] last_block_point = GENESIS_LINK for k in range(num_blocks): blk = FakeBlock(com_id=com_id, links=last_block_point, transaction=None) blocks.append(blk) last_block_point = Links(((blk.com_seq_num, blk.short_hash),)) return blocks
def test_insert_with_merge_block(self, create_batches, insert_function, chain): batches = create_batches(num_batches=2, num_blocks=10) last_blk1 = batches[0][-1] last_blk2 = batches[1][-1] dot1 = (last_blk1.com_seq_num, last_blk1.short_hash) dot2 = (last_blk2.com_seq_num, last_blk2.short_hash) vals = wrap_return(insert_function(chain, batches[0])) assert len(vals) == 10 assert vals[0][0] == 1 and vals[-1][0] == 10 merge_block = FakeBlock(links=Links((dot1, dot2))) chain.add_block(merge_block.links, merge_block.com_seq_num, merge_block.hash) vals = wrap_return(insert_function(chain, batches[1])) assert len(vals) == 11 assert vals[0][0] == 1 and vals[-1][0] == 11 assert len(list(chain.get_dots_by_seq_num(11))) == 1
def test_add_claim(self): value = Decimal(12.11, self.con) dot = Dot((1, "123123")) chain_id = self.spender self.state.apply_spend( chain_id, GENESIS_LINK, GENESIS_LINK, dot, self.spender, self.receiver, value, ) assert self.state.last_spend_values[self.spender][self.receiver][dot] == value assert float(self.state.get_balance(self.spender)) == -12.11 claim_dot = Dot((1, "2323")) self.state.apply_confirm( chain_id, self.receiver, Links((dot,)), claim_dot, self.spender, dot, value ) assert self.state.last_spend_values[self.spender][self.receiver][dot] == value assert float(self.state.get_balance(self.receiver)) == 12.11
def test_mint_and_spend(self): chain_id = self.minter value = Decimal(15.00, self.con) dot = Dot((1, "123123")) self.state.apply_mint(chain_id, dot, GENESIS_LINK, self.minter, value) assert self.state.get_balance(self.minter) == value assert not self.state.is_chain_forked(chain_id, self.minter) spend_value = Decimal(12.00, self.con) spend_dot = Dot((2, "23123")) self.state.apply_spend( chain_id, GENESIS_LINK, Links((dot,)), spend_dot, self.spender, self.receiver, spend_value, ) assert float(self.state.get_balance(self.spender)) == 3 assert not self.state.is_chain_forked(chain_id, self.spender) assert not self.state.was_chain_forked(chain_id, self.spender) return chain_id, spend_value, spend_dot
def create( cls, block_type: bytes, transaction: bytes, database: BaseDB, public_key: bytes, com_prefix: bytes = b"", com_id: bytes = None, com_links: Links = None, pers_links: Links = None, use_consistent_links: bool = True, ): """ Create PlexusBlock wrt local database knowledge. Args: block_type: type of the block in bytes transaction: transaction blob bytes database: local database with chains public_key: public key of the block creator com_prefix: prefix for the chain identification [optional] com_id: id of the community which block is part of [optional] com_links: Explicitly link with these blocks [optional] pers_links: Create a block at a certain [optional] use_consistent_links: Build on top of blocks that are known. By default: True Returns: PlexusBlock """ if public_key == com_id: full_pers_chain_id = com_prefix + public_key else: full_pers_chain_id = public_key personal_chain = database.get_chain(full_pers_chain_id) # Decide to link blocks in the personal chain: if not personal_chain: # There are no blocks in the personal chain yet last_link = Links((GENESIS_DOT, )) else: last_link = (personal_chain.consistent_terminal if use_consistent_links else personal_chain.terminal) # Fork personal chain at the if pers_links: # There is an explicit link for the previous link last_link = pers_links per_seq_num = max(last_link)[0] + 1 # TODO: Add link filtering and choose links ret = cls() ret.type = block_type ret.transaction = transaction ret.sequence_number = per_seq_num ret.previous = last_link # --- Community related logic --- if com_id: ret.com_id = com_id # There is community specified => will base block on the latest known information + filters if com_links: last_com_links = com_links com_seq_num = max(last_com_links)[0] else: com_chain = database.get_chain(com_prefix + com_id) if not com_chain: last_com_links = Links((GENESIS_DOT, )) else: last_com_links = (com_chain.consistent_terminal if use_consistent_links else com_chain.terminal) # TODO: add link filtering here com_seq_num = max(last_com_links)[0] + 1 ret.links = last_com_links ret.com_seq_num = com_seq_num ret.com_id = com_id ret.com_prefix = com_prefix ret._links = encode_links(ret.links) ret._previous = encode_links(ret.previous) ret.public_key = public_key ret.signature = EMPTY_SIG ret.hash = ret.calculate_hash() return ret
def test_encode_decode_links(keys_fixture): links = Links(((1, shorten(keys_fixture)), )) raw_bytes = encode_links(links) assert decode_links(raw_bytes) == links
def test_empty_terminal(self): chain = Chain() assert chain.consistent_terminal == Links((GENESIS_DOT, ))
def test_add_invalid_claim(self): value = Decimal(1, self.con) dot = Dot((1, "123123")) chain_id = self.spender self.state.apply_spend( chain_id, GENESIS_LINK, GENESIS_LINK, dot, self.spender, self.receiver, value, ) assert self.state.last_spend_values[self.spender][self.receiver][dot] == value assert float(self.state.get_balance(self.spender)) == -1 new_dot = Dot((1, "223123")) self.state.apply_spend( chain_id, GENESIS_LINK, GENESIS_LINK, new_dot, self.spender, self.receiver, value, ) assert self.state.last_spend_values[self.spender][self.receiver][dot] == value assert float(self.state.get_balance(self.spender)) == -2 assert self.state.was_chain_forked(chain_id, self.spender) claim_dot = Dot((2, "33323")) with pytest.raises(InvalidClaimException): # Should raise exception as the claim links are not correct self.state.apply_confirm( chain_id, self.receiver, GENESIS_LINK, claim_dot, self.spender, dot, value, ) self.state.apply_confirm( chain_id, self.receiver, Links((dot,)), claim_dot, self.spender, dot, value ) assert self.state.last_spend_values[self.spender][self.receiver][dot] == value assert float(self.state.get_balance(self.receiver)) == 1 with pytest.raises(InvalidClaimException): # Double claim - should raise exception self.state.apply_confirm( chain_id, self.receiver, Links((claim_dot,)), claim_dot, self.spender, dot, value, ) assert float(self.state.get_balance(self.receiver)) == 1 assert not self.state.was_chain_forked(chain_id, self.receiver) # Add inconsistent claim inconsistent_value = Decimal(100, self.con) with pytest.raises(InconsistentClaimException): self.state.apply_confirm( chain_id, self.receiver, Links((claim_dot,)), claim_dot, self.spender, new_dot, inconsistent_value, ) assert float(self.state.get_balance(self.receiver)) == 1 assert not self.state.was_chain_forked(chain_id, self.receiver)
def test_state_updates(self): chain_id = self.minter value = Decimal(12.00, self.con) dot = Dot((1, b"123123")) self.state.apply_mint( chain_id, dot, GENESIS_LINK, self.minter, value, store_update=True ) assert self.state.get_balance(self.minter) == value assert not self.state.is_chain_forked(chain_id, self.minter) spend_value = Decimal(12.00, self.con) spend_dot = Dot((2, b"23123")) self.state.apply_spend( chain_id, GENESIS_LINK, Links((dot,)), spend_dot, self.spender, self.receiver, spend_value, store_status_update=True, ) assert float(self.state.get_balance(self.spender)) == 0 assert not self.state.is_chain_forked(chain_id, self.spender) claim_dot = Dot((3, b"23323")) self.state.apply_confirm( chain_id, self.receiver, Links((spend_dot,)), claim_dot, self.spender, spend_dot, spend_value, store_update=True, ) v = self.state.get_closest_peers_status(chain_id, 1) assert v is not None assert v[0] == 1 assert len(v[1]) == 1 assert v[1].get(shorten(self.spender)) == (True, True) v = self.state.get_closest_peers_status(chain_id, 2) assert v is not None assert ( (v[0] == 2) and (len(v[1]) == 1) and (v[1].get(shorten(self.spender)) == (True, True)) ) v = self.state.get_closest_peers_status(chain_id, 3) assert v is not None assert ( v[0] == 3 and len(v[1]) == 2 and (v[1].get(shorten(self.spender)) == (True, True)) and (v[1].get(shorten(self.receiver)) == (True, True)) ) assert v[1] == self.state.get_last_peer_status(chain_id)
def test_empty_terminal(self, chain): assert chain.consistent_terminal == Links((GENESIS_DOT, ))