def apply_witness_tx( self, block: BamiBlock, witness_tx: Tuple[int, ChainState] ) -> None: state = witness_tx[1] state_hash = take_hash(state) seq_num = witness_tx[0] if not self.should_witness_chain_point(block.com_id, block.public_key, seq_num): # This is invalid witnessing - react raise InvalidWitnessTransactionException( "Received invalid witness transaction", block.com_id, block.public_key, seq_num, ) self.state_db.add_witness_vote( block.com_id, seq_num, state_hash, block.public_key ) self.state_db.add_chain_state(block.com_id, seq_num, state_hash, state) chain_id = block.com_id if self.tracked_blocks.get(chain_id): for block_dot, tracked_block in self.tracked_blocks[chain_id].items(): if ( block_dot[0] <= seq_num and state.get(shorten(tracked_block.public_key)) and state.get(shorten(tracked_block.public_key)) == (True, True) ): self.update_risk(chain_id, block.public_key, block_dot[0])
def witness(self, chain_id: bytes, seq_num: int) -> None: """ Witness the chain up to a sequence number. If chain is known and data exists: - Will create a witness block, link to latest known blocks and share in the community. Otherwise: - Do nothing Args: chain_id: id of the chain seq_num: sequence number of the block: """ chain = self.persistence.get_chain(chain_id) if chain: witness_blob = self.build_witness_blob(chain_id, seq_num) if witness_blob: blk = self.create_signed_block( block_type=WITNESS_TYPE, transaction=witness_blob, prefix=b"w", com_id=chain_id, use_consistent_links=False, ) self.logger.debug( "Creating witness block on chain %s: %s, com_dot %s, pers_dot %s", shorten(blk.com_id), seq_num, blk.com_dot, blk.pers_dot, ) self.share_in_community(blk, chain_id)
def block_response( self, block: BamiBlock, wait_time: float = None, wait_blocks: int = None ) -> BlockResponse: # Analyze the risk of accepting this block stat = self.state_db.get_closest_peers_status(block.com_id, block.com_seq_num) # If there is no information or chain is forked or peer_id = shorten(block.public_key) if not stat or not stat[1].get(peer_id): # Check that it is not infinite if (wait_time and wait_time > self.settings.max_wait_time) or ( wait_blocks and wait_blocks > self.settings.max_wait_block ): return BlockResponse.REJECT return BlockResponse.DELAY if not stat[1][peer_id][1] or not stat[1][peer_id][0]: # If chain is forked or negative balance => reject return BlockResponse.REJECT # Verify the risk of missing some information: # - There is diverse peers building upon the block # TODO: revisit that - number should depend on total number of peers in community. # 1. Diversity on the block building f = self.settings.diversity_confirm if len(self.peer_conf[(block.com_id, block.com_seq_num)]) >= f: return BlockResponse.CONFIRM else: return BlockResponse.DELAY
def get_last_peer_status(self, chain_id: bytes) -> ChainState: """Get last balance of peers in the community""" v = dict() for p in self.chain_peers[chain_id]: v[shorten(p)] = ( self.get_balance(p) >= 0, not self.is_chain_forked(chain_id, p), ) return ChainState(v)
def __str__(self): # This makes debugging and logging easier return "Block {0} from ...{1}:{2} links {3} for {4} type {5} cseq {6} cid {7}.{8}".format( self.short_hash, shorten(self.public_key), self.sequence_number, self.links, self.transaction, self.type, self.com_seq_num, self.com_prefix, self.com_id, )
def short_hash(self): return shorten(self.hash)
def update_risk(self, chain_id: bytes, conf_peer_id: bytes, target_seq_num: int): print("Risk update: ", shorten(conf_peer_id), target_seq_num) self.peer_conf[(chain_id, target_seq_num)][conf_peer_id] += 1
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_shorten_size(keys_fixture): s_k = shorten(keys_fixture) assert len(s_k) == KEY_LEN
def test_short_hash(self): block = FakeBlock() assert shorten(block.hash) == block.short_hash
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)