def test_correct_root_calculation_at_one(self): reference_tree = TransactionMerkleTree(self.transactions_1[:1]) merkle_hash_cache, merkle_height_cache = '', '' root_hash = '' for tx in self.transactions_2[:1]: optimized_tree = OptimizedTransactionMerkleTree( merkle_hash_cache, merkle_height_cache, tx) root_hash = optimized_tree.root_hash() merkle_hash_cache, merkle_height_cache = optimized_tree.merkle_cache_stacks( ) self.assertEqual(root_hash, reference_tree.root_hash())
def test_correct_root_calculation_at_zero(self): reference_tree = TransactionMerkleTree([]) merkle_hash_cache, merkle_height_cache = '', '' root_hash = '' optimized_tree = OptimizedTransactionMerkleTree( merkle_hash_cache, merkle_height_cache) root_hash = optimized_tree.root_hash() merkle_hash_cache, merkle_height_cache = optimized_tree.merkle_cache_stacks( ) self.assertEqual(root_hash, reference_tree.root_hash())
def test_correct_root_calculation_batch_create(self): reference_tree = TransactionMerkleTree(self.transactions_1) merkle_hash_cache, merkle_height_cache = '', '' root_hash = '' optimized_tree = OptimizedTransactionMerkleTree( merkle_hash_cache, merkle_height_cache, transactions=self.transactions_2) root_hash = optimized_tree.root_hash() merkle_hash_cache, merkle_height_cache = optimized_tree.merkle_cache_stacks( ) self.assertEqual(root_hash, reference_tree.root_hash())
def test_last_transfer_index_at_one(self): reference_tree = TransactionMerkleTree(self.transactions_1[:1]) merkle_hash_cache, merkle_height_cache = '', '' optimized_tree = None for tx in self.transactions_2[:1]: optimized_tree = OptimizedTransactionMerkleTree( merkle_hash_cache, merkle_height_cache, tx) merkle_hash_cache, merkle_height_cache = optimized_tree.merkle_cache_stacks( ) self.assertEqual(optimized_tree.root_hash(), reference_tree.root_hash()) self.assertEqual( optimized_tree.last_tx_index(), reference_tree.merkle_tree_nonce_map.get( self.transactions_2[0].get('nonce')))
def test_last_transfer_path(self): reference_tree = TransactionMerkleTree(self.transactions_1) merkle_hash_cache, merkle_height_cache = '', '' optimized_tree = None self for tx in self.transactions_2: optimized_tree = OptimizedTransactionMerkleTree( merkle_hash_cache, merkle_height_cache, tx) merkle_hash_cache, merkle_height_cache = optimized_tree.merkle_cache_stacks( ) last_tx_index = optimized_tree.last_tx_index() self.assertEqual(optimized_tree.root_hash(), reference_tree.root_hash()) self.assertEqual( last_tx_index, reference_tree.merkle_tree_nonce_map.get( self.transactions_2[-1].get('nonce'))) self.assertEqual( optimized_tree.merkle_tree_nonce_map.get( self.transactions_2[-1].get('nonce')), reference_tree.merkle_tree_nonce_map.get( self.transactions_2[-1].get('nonce'))) self.assertEqual(optimized_tree.last_tx_proof(), reference_tree.proof(last_tx_index))
def optimized_authorized_transfers_tree( self, force_append=True, only_appended=False, starting_balance=None, assume_active_state_exists=False ) -> OptimizedTransactionMerkleTree: # fetch last 2 transfers in active set transfers, with cache values last_cached_transfer, before_last_cached_transfer = self.get_last_two_cached_transactions( only_appended, force_append) is_adding_new_transaction = self.transfer is not None and not self.transfer.appended and ( self.transfer.wallet_id == self.wallet.id or not self.transfer.passive) is_fetching_complete_set = not force_append is_fetching_appended_set = force_append and only_appended and self.transfer is not None and self.transfer.id is not None # if the last cache entry exists if last_cached_transfer is not None: # fetching complete transaction set if is_fetching_complete_set: # need second last cache item to construct tree if before_last_cached_transfer is not None: return OptimizedTransactionMerkleTree( before_last_cached_transfer.hash_cache, before_last_cached_transfer.height_cache, transaction=last_cached_transfer.shorthand( wallet_transfer_context=self, is_last_transfer=True, starting_balance=starting_balance, assume_active_state_exists= assume_active_state_exists)) # if it does not exist, fallback to recalculating entire tree # fetch only appended transaction set elif is_fetching_appended_set: # if last cached transaction is context transaction if last_cached_transfer.id == self.transfer.id: # need second last cache item to construct tree if before_last_cached_transfer is not None: return OptimizedTransactionMerkleTree( before_last_cached_transfer.hash_cache, before_last_cached_transfer.height_cache, transaction=self.transfer.shorthand( wallet_transfer_context=self, is_last_transfer=True, starting_balance=starting_balance, assume_active_state_exists= assume_active_state_exists)) # if it does not exist, fallback to recalculating entire tree # if last cached transaction is not context transaction # use last cached item and context transaction to calculate tree else: return OptimizedTransactionMerkleTree( last_cached_transfer.hash_cache, last_cached_transfer.height_cache, transaction=self.transfer.shorthand( wallet_transfer_context=self, is_last_transfer=True, starting_balance=starting_balance, assume_active_state_exists= assume_active_state_exists)) # adding new transaction to set elif is_adding_new_transaction: # get last cached items merkle_hash_cache = last_cached_transfer.hash_cache merkle_height_cache = last_cached_transfer.height_cache # is this context transaction a fullfilled or cancelled swap # need to reconstruct for the sender as well # a partially matched swap in a previous eon will result in a different starting balance (from that of sender state tx set) # if multi eon swap is cancelled, since starting balance changed this tx_set_tree should be reconstructed is_fulfilled_or_cancelled_swap = last_cached_transfer.is_swap( ) and (last_cached_transfer.complete or last_cached_transfer.cancelled) # is this context transaction passive is_passive = last_cached_transfer.passive # reconstruct tree using existing tx set, excluding context transaction # to make sure swap or passive transfer is finalized should_reconstruct_old_tree = is_fulfilled_or_cancelled_swap or is_passive if should_reconstruct_old_tree: # for backward compatibility # if tree before last tree is not cached, re-calculate entire tree if before_last_cached_transfer is None: old_set = self.authorized_transfers_list( only_appended=False, force_append=False, ) reconstructed_old_tree = OptimizedTransactionMerkleTree( merkle_hash_cache=None, merkle_height_cache=None, transactions=[ tx.shorthand(wallet_transfer_context=self, starting_balance=starting_balance, assume_active_state_exists= assume_active_state_exists) for tx in old_set if tx.index <= last_cached_transfer.index ]) # if tree before last tree is cached, use cache to re-construct last tree else: reconstructed_old_tree = OptimizedTransactionMerkleTree( before_last_cached_transfer.hash_cache, before_last_cached_transfer.height_cache, transaction=last_cached_transfer.shorthand( wallet_transfer_context=self, is_last_transfer=False, starting_balance=starting_balance, assume_active_state_exists= assume_active_state_exists)) # update last cache item after finalizing included transactions merkle_hash_cache, merkle_height_cache = reconstructed_old_tree.merkle_cache_stacks( ) # if context wallet is sender if self.wallet == last_cached_transfer.wallet: last_cached_transfer.sender_merkle_hash_cache = merkle_hash_cache last_cached_transfer.sender_merkle_height_cache = merkle_height_cache # if context wallet is recipient and is_fulfilled_or_cancelled_swap elif self.wallet == last_cached_transfer.recipient and is_fulfilled_or_cancelled_swap: last_cached_transfer.recipient_merkle_hash_cache = merkle_hash_cache last_cached_transfer.recipient_merkle_height_cache = merkle_height_cache last_cached_transfer.save() # use last cached item to construct new tree return OptimizedTransactionMerkleTree( merkle_hash_cache, merkle_height_cache, transaction=self.transfer.shorthand( wallet_transfer_context=self, starting_balance=starting_balance, assume_active_state_exists=assume_active_state_exists)) # for backward compatibility # fallback to re-calculating the whole tree return OptimizedTransactionMerkleTree( merkle_hash_cache=None, merkle_height_cache=None, transactions=self.authorized_transfers_list_shorthand( only_appended=only_appended, force_append=force_append, last_transfer_is_finalized=not (is_fetching_appended_set or is_adding_new_transaction), starting_balance=starting_balance, assume_active_state_exists=assume_active_state_exists))
def optimized_authorized_transfers_tree_from_list( cls, transfers_list) -> OptimizedTransactionMerkleTree: return OptimizedTransactionMerkleTree(merkle_hash_cache=None, merkle_height_cache=None, transactions=transfers_list)