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
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 add_block(self, block_blob: bytes, block: "PlexusBlock") -> None: block_hash = block.hash block_tx = block.transaction # 1. Add block blob and transaction blob to the block storage self.block_store.add_block(block_hash, block_blob) self.block_store.add_tx(block_hash, block_tx) self.block_store.add_extra(block_hash, encode_raw({b"type": block.type})) # 2. There are two chains: personal and community chain pers = block.public_key com = block.com_id if pers == com: pers = block.com_prefix + pers com = block.com_prefix + com else: com = block.com_prefix + com # 2.1: Process the block wrt personal chain if pers not in self.chains: self.chains[pers] = self.chain_factory.create_chain() pers_block_dot = Dot((block.sequence_number, block.short_hash)) pers_dots_list = self.chains[pers].add_block(block.previous, block.sequence_number, block_hash) full_dot_id = pers + encode_raw(pers_block_dot) self.block_store.add_dot(full_dot_id, block_hash) # TODO: add more chain topic # Notify subs of the personal chain self.notify(ChainTopic.ALL, chain_id=pers, dots=pers_dots_list) self.notify(ChainTopic.PERSONAL, chain_id=pers, dots=pers_dots_list) self.notify(pers, chain_id=pers, dots=pers_dots_list) # 2.2: add block to the community chain if com != EMPTY_PK: if com == pers: # Chain was processed already, notify rest self.notify(ChainTopic.GROUP, chain_id=com, dots=pers_dots_list) else: if com not in self.chains: self.chains[com] = self.chain_factory.create_chain() com_block_dot = Dot((block.com_seq_num, block.short_hash)) com_dots_list = self.chains[com].add_block( block.links, block.com_seq_num, block_hash) full_dot_id = com + encode_raw(com_block_dot) self.block_store.add_dot(full_dot_id, block_hash) self.notify(ChainTopic.ALL, chain_id=com, dots=com_dots_list) self.notify(ChainTopic.GROUP, chain_id=com, dots=com_dots_list) self.notify(com, chain_id=com, dots=com_dots_list)
def spend( self, chain_id: bytes, counter_party: bytes, value: Decimal, ignore_validation: bool = False, ) -> None: """ Spend tokens in the chain to the counter_party. Args: chain_id: identity of the chain counter_party: identity of the counter-party value: Decimal value to transfer ignore_validation: if True and balance is negative - will raise an Exception """ bal = self.state_db.get_balance(self.my_pub_key_bin) if ignore_validation or bal - value >= 0: spend_tx = { b"value": float(value), b"to_peer": counter_party, b"prev_pairwise_link": self.state_db.get_last_pairwise_links( self.my_pub_key_bin, counter_party ), } self.verify_spend(self.my_pub_key_bin, spend_tx) block = self.create_signed_block( block_type=SPEND_TYPE, transaction=encode_raw(spend_tx), com_id=chain_id ) self.logger.info("Created spend block %s", block.com_dot) counter_peer = self.get_peer_by_key(counter_party, chain_id) if counter_peer: self.send_block(block, [counter_peer]) self.share_in_community(block, chain_id) else: raise InsufficientBalanceException("Not enough balance for spend")
def get_block_blob_by_dot(self, chain_id: bytes, block_dot: Dot) -> Optional[bytes]: dot_id = chain_id + encode_raw(block_dot) blk_hash = self.block_store.get_hash_by_dot(dot_id) if blk_hash: return self.block_store.get_block_by_hash(blk_hash) else: return None
def get_extra_by_dot(self, chain_id: bytes, block_dot: Dot) -> Optional[bytes]: dot_id = chain_id + encode_raw(block_dot) hash_val = self.block_store.get_hash_by_dot(dot_id) if hash_val: return self.block_store.get_extra(hash_val) else: return None
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_decimal(): new_con = getcontext() new_con.prec = 4 t = Decimal(2.191, new_con) t2 = Decimal(2.11, new_con) l = encode_raw({b"value": float(t2)}) p = decode_raw(l) assert p.get(b"value") == float(t2)
async def test_block_confirm(set_vals): """ Test whether blocks are confirmed correctly. """ block = set_vals.nodes[0].overlay.create_signed_block( com_id=set_vals.community_id, transaction=encode_raw({b"to_peer": 3}), block_type=b"good", ) set_vals.nodes[0].overlay.share_in_community(block, set_vals.community_id) await deliver_messages()
def reject(self, block: BamiBlock, extra_data: Dict = None) -> None: # change it to confirm # create claim block and share in the community chain_id = block.com_id if block.com_id != EMPTY_PK else block.public_key dot = block.com_dot if block.com_id != EMPTY_PK else block.pers_dot reject_tx = {b"initiator": block.public_key, b"dot": dot} if extra_data: reject_tx.update(extra_data) block = self.create_signed_block( block_type=REJECT_TYPE, transaction=encode_raw(reject_tx), com_id=chain_id ) self.share_in_community(block, chain_id)
def confirm(self, block: BamiBlock, extra_data: Dict = None) -> None: """Create confirm block linked to block. Link will be in the transaction with block dot. Add extra data to the transaction with a 'extra_data' dictionary. """ chain_id = block.com_id if block.com_id != EMPTY_PK else block.public_key dot = block.com_dot if block.com_id != EMPTY_PK else block.pers_dot confirm_tx = {b"initiator": block.public_key, b"dot": dot} if extra_data: confirm_tx.update(extra_data) block = self.create_signed_block( block_type=CONFIRM_TYPE, transaction=encode_raw(confirm_tx), com_id=chain_id ) self.share_in_community(block, chain_id)
def std_vals(self): self.chain_id = b"chain_id" self.block_dot = Dot((3, ShortKey("808080"))) self.block_dot_encoded = encode_raw(self.block_dot) self.dot_id = self.chain_id + self.block_dot_encoded self.test_hash = b"test_hash" self.tx_blob = b"tx_blob" self.block_blob = b"block_blob" self.test_block = FakeBlock() self.pers = self.test_block.public_key self.com_id = self.test_block.com_id
def __init__( self, transaction: bytes = None, previous: Links = None, key: LibNaCLSK = None, links: Links = None, com_prefix: bytes = b"", com_id: Any = None, block_type: bytes = b"test", ): crypto = default_eccrypto if not links: links = GENESIS_LINK com_seq_num = 1 else: com_seq_num = max(links)[0] + 1 if not previous: previous = GENESIS_LINK pers_seq_num = max(previous)[0] + 1 if not com_id: com_id = crypto.generate_key(u"curve25519").pub().key_to_bin() if key: self.key = key else: self.key = crypto.generate_key(u"curve25519") if not transaction: transaction = encode_raw({"id": 42}) BamiBlock.__init__( self, ( block_type, transaction, self.key.pub().key_to_bin(), pers_seq_num, encode_links(previous), encode_links(links), com_prefix, com_id, com_seq_num, EMPTY_SIG, 0, 0, ), ) self.sign(self.key)
def request_state( self, peer: Peer, chain_id: bytes, state_id: Any = None, include_other_witnesses: bool = True, ): self.logger.debug("Requesting state from a peer (%s) ", peer) state_request = { "state": state_id, "include_others": include_other_witnesses } self.send_packet(peer, StateRequestPayload(chain_id, encode_raw(state_request)), sig=True)
def mint(self, value: Decimal = None, chain_id: bytes = None) -> None: """ Create mint for own reputation: Reputation & Liveness at Stake """ if not value: value = self.settings.initial_mint_value if not chain_id: # Community id is the same as the peer id chain_id = self.my_pub_key_bin # Mint transaction: value mint_tx = {b"value": float(value)} self.verify_mint(chain_id, self.my_pub_key_bin, mint_tx) block = self.create_signed_block( block_type=MINT_TYPE, transaction=encode_raw(mint_tx), com_id=chain_id ) self.share_in_community(block, chain_id)
def test_val(self): val = Decimal(10, self.con) l = encode_raw({b"123": float(val)}) d = decode_raw(l) print(d) l = cachetools.LRUCache(10) l[1] = "v" int_max = 9999 k = 4 try: v = min(l.keys(), key=lambda x: abs(x - k) if x >= k else int_max) if v < k: # No data yet: pass except ValueError: print("No data for the chain")
def create_introduction_response( self, lan_socket_address, socket_address, identifier, introduction=None, extra_bytes=b"", prefix=None, ): extra_bytes = encode_raw(self.my_subcoms) return super().create_introduction_response( lan_socket_address, socket_address, identifier, introduction, extra_bytes, prefix, )
def create_signed_block( self, block_type: bytes = b"unknown", transaction: Optional[bytes] = None, prefix: bytes = b"", com_id: bytes = None, links: Links = None, personal_links: Links = None, use_consistent_links: bool = True, ) -> BamiBlock: """ This function will create, sign, persist block with given parameters. Args: block_type: bytes of the block transaction: bytes blob of the transaction, or None to indicate an empty transaction payload prefix: prefix for the community id. For example b'w' - for witnessing transactions com_id: sub-community id if applicable links: explicitly link to certain links in the sub-community. Warning - may lead to forks! personal_links: explicitly link to certain blocks in the own chain. Warning - may lead to forks! use_consistent_links (): Returns: signed block """ if not transaction: transaction = encode_raw(b"") block = BamiBlock.create( block_type, transaction, self.persistence, self.my_pub_key_bin, com_id=com_id, com_links=links, pers_links=personal_links, com_prefix=prefix, use_consistent_links=use_consistent_links, ) block.sign(self.my_peer_key) self.validate_persist_block(block, self.my_peer) return block
def build_witness_blob(self, chain_id: bytes, seq_num: int) -> Optional[bytes]: chain_state = self.state_db.get_closest_peers_status(chain_id, seq_num) if not chain_state: return None return encode_raw(chain_state)
def create_introduction_request( self, socket_address: Any, extra_bytes: bytes = b"" ): extra_bytes = encode_raw(self.my_subcoms) return super().create_introduction_request(socket_address, extra_bytes)
def test_encode_decode_bytelist(): vals = {b"1", b"2", b"3", b"4", b"100", b"10", b"21", b"5"} assert set(decode_raw(encode_raw(list(vals)))) == vals
def test_encode_decode_raw(): vals = {b"id": 42} assert decode_raw(encode_raw(vals))[b"id"] == 42
def notify_peers_on_new_subcoms(self) -> None: for peer in self.get_peers(): self.send_packet( peer, SubscriptionsPayload(self.my_pub_key_bin, encode_raw(self.my_subcoms)), )
def to_bytes(self) -> bytes: return encode_raw({ b"t": self.terminal, b"h": self.holes, b"i": self.inconsistencies })
def to_bytes(self) -> bytes: return encode_raw({b"m": self.missing, b"c": self.conflicts})