All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hazelcast.wan.impl.merkletree.MerkleTreeUtil Maven / Gradle / Ivy

/*
 * Copyright (c) 2008-2020, Hazelcast, Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.hazelcast.wan.impl.merkletree;

import com.hazelcast.internal.util.QuickMath;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.Collection;
import java.util.LinkedList;

import static com.hazelcast.internal.util.Preconditions.checkTrue;

/**
 * Utility methods for Merkle tree implementation
 */
public final class MerkleTreeUtil {
    static final int HUGE_PRIME = 2038079003;
    @SuppressWarnings("checkstyle:magicnumber")
    private static final long INT_RANGE = 1L << 32;

    private MerkleTreeUtil() {
    }

    /**
     * Returns the breadth-first order of the leaf that a given {@code hash}
     * belongs to
     *
     * @param hash  The hash for which the leaf order to be calculated
     * @param level The level
     * @return the breadth-first order of the leaf for the given {@code hash}
     */
    public static int getLeafOrderForHash(int hash, int level) {
        long hashStepForLevel = getNodeHashRangeOnLevel(level);
        long hashDistanceFromMin = ((long) hash) - Integer.MIN_VALUE;
        int steps = (int) (hashDistanceFromMin / hashStepForLevel);
        int leftMostNodeOrderOnLevel = getLeftMostNodeOrderOnLevel(level);

        return leftMostNodeOrderOnLevel + steps;
    }

    /**
     * Returns the hash range that one node covers on the given {@code level}
     *
     * @param level The level the node hash range to be calculated for
     * @return the hash range of a node on the given {@code level}
     */
    static long getNodeHashRangeOnLevel(int level) {
        int nodesOnLevel = getNodesOnLevel(level);
        return INT_RANGE / nodesOnLevel;
    }

    /**
     * Returns the lower bound of the hash range for a given node
     *
     * @param nodeOrder The breadth-first order of the node
     * @return the lower bound of the hash range
     */
    static int getNodeRangeLow(int nodeOrder) {
        int level = getLevelOfNode(nodeOrder);
        int leftMostLeafOrder = getLeftMostNodeOrderOnLevel(level);
        int levelHashStep = (int) getNodeHashRangeOnLevel(level);
        int leafOrderOnLevel = nodeOrder - leftMostLeafOrder;

        return Integer.MIN_VALUE + (leafOrderOnLevel * levelHashStep);
    }

    /**
     * Returns the upper bound of the hash range for a given node
     *
     * @param nodeOrder The breadth-first order of the node
     * @return the upper bound of the hash range
     */
    static int getNodeRangeHigh(int nodeOrder) {
        int level = getLevelOfNode(nodeOrder);
        int leftMostLeafOrder = getLeftMostNodeOrderOnLevel(level);
        int levelHashStep = (int) getNodeHashRangeOnLevel(level);
        int leafOrderOnLevel = nodeOrder - leftMostLeafOrder;

        return Integer.MIN_VALUE + ((leafOrderOnLevel + 1) * levelHashStep) - 1;
    }

    /**
     * Adds a hash to the accumulated hash of a tree node
     * 

* Notes: *

    *
  • The hash function must be associative. For the detailed * reasoning see {@link MerkleTree} *
  • {@link #addHash(int, int)} and {@link #removeHash(int, int)} is * distinguished since the result accumulated hash must be the same if * an entry's hash is added and then removed and if the entry is not * added. This distinction acts as an optimisation, since this way * iterating over the entries and their hashes belong to a leaf is not * required when a leaf's hash is updated. *
* * @param originalHash The accumulated hash before {@code addedHash} * is added * @param addedHash The hash to be added to the accumulated hash * @return the accumulated hash with {@code addedHash} added * @see #removeHash(int, int) */ static int addHash(int originalHash, int addedHash) { return originalHash + HUGE_PRIME * addedHash; } /** * Removes a hash from the accumulated hash of a tree node *

* Notes: *

    *
  • The hash function must be associative. For the detailed * reasoning see {@link MerkleTree} *
  • {@link #addHash(int, int)} and {@link #removeHash(int, int)} is * distinguished since the result accumulated hash must be the same if * an entry's hash is added and then removed and if the entry is not * added. This distinction acts as an optimisation, since this way * iterating over the entries and their hashes belong to a leaf is not * required when a leaf's hash is updated. *
* * @param originalHash The accumulated hash before {@code removedHash} * is removed * @param removedHash The hash to be removed from the accumulated hash * @return the accumulated hash with {@code removedHash} removed * @see #addHash(int, int) */ static int removeHash(int originalHash, int removedHash) { return originalHash - HUGE_PRIME * removedHash; } /** * Sums the hash of the left and the right children *

* May be used only when calculating a non-leaf node's hash code from * its children's hashes. * * @param leftHash The hash of the left child * @param rightHash The hash of the right child * @return the accumulated hash * @see #addHash(int, int) * @see #removeHash(int, int) */ public static int sumHash(int leftHash, int rightHash) { return leftHash + rightHash; } /** * Returns the level a node with the given {@code order} is on in the tree * * @param nodeOrder The breadth-first order of a node for which its level * to be calculated * @return the level which the node with {@code order} is on */ public static int getLevelOfNode(int nodeOrder) { return QuickMath.log2(nodeOrder + 1); } /** * Returns the breadth-first order of the leftmost node on a given {@code level} * * @param level The level for which the leftmost node's order to be * calculated * @return the order of the leftmost node on the given level */ static int getLeftMostNodeOrderOnLevel(int level) { return (1 << level) - 1; } /** * Returns the number of nodes on the provided {@code level} * * @param level The level * @return the number of the nodes on the given level */ public static int getNodesOnLevel(int level) { return 1 << level; } /** * Returns the breadth-first order of the parent node of the node * with {@code nodeOrder} * * @param nodeOrder The order of the node for which its parent's order * to be calculated * @return the order of the parent node */ public static int getParentOrder(int nodeOrder) { return (nodeOrder - 1) >> 1; } /** * Returns the breadth-first order of the left child node of the node * with {@code nodeOrder} * * @param nodeOrder The order of the node for which its left child's * order to be calculated * @return the order of the left child node */ public static int getLeftChildOrder(int nodeOrder) { return (nodeOrder << 1) + 1; } /** * Returns the breadth-first order of the right child node of the node * with {@code nodeOrder} * * @param nodeOrder The order of the node for which its right child's * order to be calculated * @return the order of the right child node */ public static int getRightChildOrder(int nodeOrder) { return (nodeOrder << 1) + 2; } /** * Returns the number of nodes in a tree with {@code depth} levels. * * @param depth The depth of the tree * @return the nodes in the tree */ static int getNumberOfNodes(int depth) { return (1 << depth) - 1; } /** * Returns true if the node with {@code nodeOrder} is a leaf in a tree * with {@code depth} levels * * @param nodeOrder The order of the node to test * @param depth The depth of the tree * @return true if the node is leaf, false otherwise */ static boolean isLeaf(int nodeOrder, int depth) { checkTrue(depth > 0, "Invalid depth: " + depth); int leafLevel = depth - 1; int numberOfNodes = getNumberOfNodes(depth); int maxNodeOrder = numberOfNodes - 1; checkTrue(nodeOrder >= 0 && nodeOrder <= maxNodeOrder, "Invalid nodeOrder: " + nodeOrder + " in a tree with depth " + depth); int leftMostLeafOrder = MerkleTreeUtil.getLeftMostNodeOrderOnLevel(leafLevel); return nodeOrder >= leftMostLeafOrder; } /** * Returns the breadth-first order of the leftmost leaf in the * subtree selected by {@code nodeOrder} as the root of the subtree * * @param nodeOrder The order of the node as the root of the subtree * @param depth The depth of the tree * @return the breadth-first order of the leftmost leaf under the * provided node */ static int getLeftMostLeafUnderNode(int nodeOrder, int depth) { if (isLeaf(nodeOrder, depth)) { return nodeOrder; } int leafLevel = depth - 1; int levelOfNode = getLevelOfNode(nodeOrder); int distanceFromLeafLevel = depth - levelOfNode - 1; int leftMostNodeOrderOnLevel = getLeftMostNodeOrderOnLevel(levelOfNode); int relativeLevelOrder = nodeOrder - leftMostNodeOrderOnLevel; int leftMostLeaf = getLeftMostNodeOrderOnLevel(leafLevel); return leftMostLeaf + (2 << distanceFromLeafLevel - 1) * relativeLevelOrder; } /** * Returns the breadth-first order of the rightmost leaf in the * subtree selected by {@code nodeOrder} as the root of the subtree * * @param nodeOrder The order of the node as the root of the subtree * @param depth The depth of the tree * @return the breadth-first order of the rightmost leaf under the * provided node */ static int getRightMostLeafUnderNode(int nodeOrder, int depth) { if (isLeaf(nodeOrder, depth)) { return nodeOrder; } int levelOfNode = getLevelOfNode(nodeOrder); int distanceFromLeafLevel = depth - levelOfNode - 1; int leftMostLeafUnderNode = getLeftMostLeafUnderNode(nodeOrder, depth); int leavesOfSubtreeUnderNode = getNodesOnLevel(distanceFromLeafLevel); return leftMostLeafUnderNode + leavesOfSubtreeUnderNode - 1; } /** * Compares the provided local and the remote Merkle trees and * returns the breadth-first orders of the leaves that found to be * different. *

* This method compares only the leaves instead of traversing only * the subtrees that are different. This way possible hash collisions * inside the tree don't remain hidden and then syncing the clusters * can be initiated. This comes at the cost of comparing every leaf * at every comparison. * * @param local The view of the local Merkle tree * @param remote The view of the remote Merkle tree * @return the order of the leaves found to be different */ public static Collection compareTrees(MerkleTreeView local, MerkleTreeView remote) { Collection deltaOrders = new LinkedList(); MerkleTreeView baseTree = local.depth() <= remote.depth() ? local : remote; MerkleTreeView otherTree = local.depth() <= remote.depth() ? remote : local; int leafLevel = baseTree.depth() - 1; int numberOfLeaves = getNodesOnLevel(leafLevel); int leftMostLeaf = getLeftMostNodeOrderOnLevel(leafLevel); for (int leafOrder = leftMostLeaf; leafOrder < leftMostLeaf + numberOfLeaves; leafOrder++) { if (baseTree.getNodeHash(leafOrder) != otherTree.getNodeHash(leafOrder)) { deltaOrders.add(leafOrder); } } return deltaOrders; } /** * Writes the hashes of the leaves of a Merkle tree into the * provided {@link DataOutput} * * @param out The data output to write the leaves into * @param merkleTreeView The Merkle tree which leaves to be written * into the data output * @throws IOException if an I/O error occurs */ public static void writeLeaves(DataOutput out, MerkleTreeView merkleTreeView) throws IOException { int leafLevel = merkleTreeView.depth() - 1; int numberOfLeaves = getNodesOnLevel(leafLevel); int leftMostLeaf = getLeftMostNodeOrderOnLevel(leafLevel); out.writeInt(numberOfLeaves); for (int leafOrder = leftMostLeaf; leafOrder < leftMostLeaf + numberOfLeaves; leafOrder++) { out.writeInt(merkleTreeView.getNodeHash(leafOrder)); } } /** * Creates a {@link RemoteMerkleTreeView} by reading the hashes of * the leaves of a Merkle tree from the provided {@link DataInput} * * @param in The data input the hashes to be read from * @return the view representing the remote Merkle tree * @throws IOException if an I/O error occurs */ public static RemoteMerkleTreeView createRemoteMerkleTreeView(DataInput in) throws IOException { int numberOfLeaves = in.readInt(); int depth = QuickMath.log2(numberOfLeaves << 1); int[] leaves = new int[numberOfLeaves]; for (int i = 0; i < numberOfLeaves; i++) { leaves[i] = in.readInt(); } return new RemoteMerkleTreeView(leaves, depth); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy