com.swirlds.common.merkle.hash.MerkleHashChecker Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of swirlds-common Show documentation
Show all versions of swirlds-common Show documentation
Swirlds is a software platform designed to build fully-distributed applications that harness the power of the cloud without servers. Now you can develop applications with fairness in decision making, speed, trust and reliability, at a fraction of the cost of traditional server-based platforms.
/*
* Copyright (C) 2018-2024 Hedera Hashgraph, LLC
*
* 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.swirlds.common.merkle.hash;
import static com.swirlds.logging.legacy.LogMarker.EXCEPTION;
import static com.swirlds.logging.legacy.LoggingUtils.plural;
import com.swirlds.common.crypto.Cryptography;
import com.swirlds.common.crypto.CryptographyHolder;
import com.swirlds.common.crypto.Hash;
import com.swirlds.common.crypto.SerializableHashable;
import com.swirlds.common.merkle.MerkleInternal;
import com.swirlds.common.merkle.MerkleNode;
import com.swirlds.common.merkle.crypto.MerkleCryptoFactory;
import com.swirlds.common.merkle.crypto.MerkleCryptography;
import java.util.LinkedList;
import java.util.List;
import java.util.function.Consumer;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public final class MerkleHashChecker {
private static final Logger logger = LogManager.getLogger(MerkleHashChecker.class);
private MerkleHashChecker() {}
/**
* Traverses the merkle tree and checks if there are any hashes that are not valid. Recalculates all hashes
* that have been calculated externally and check them against the getHash value.
*
* @param root
* the root of the merkle tree
* @param mismatchCallback
* the method to call if a mismatch is found. May be called many times.
*/
public static void findInvalidHashes(final MerkleNode root, final Consumer mismatchCallback) {
if (root == null) {
return;
}
root.forEachNode(node -> findInvalidHash(node, mismatchCallback));
}
private static void findInvalidHash(final MerkleNode node, final Consumer mismatchCallback) {
final MerkleCryptography cryptography = MerkleCryptoFactory.getInstance();
// some nodes calculate their own hash, we could potentially check these if we serialize and deserialize
// them and then check their hash
if (node == null || node.isSelfHashing()) {
return;
}
final Hash old = node.getHash();
if (old == null) {
mismatchCallback.accept(node);
return;
}
final Hash recalculated;
if (node.isLeaf()) {
recalculated = CryptographyHolder.get()
.digestSync((SerializableHashable) node, Cryptography.DEFAULT_DIGEST_TYPE, false);
} else {
final MerkleInternal internal = node.asInternal();
for (int childIndex = 0; childIndex < internal.getNumberOfChildren(); childIndex++) {
final MerkleNode child = internal.getChild(childIndex);
if (child != null && child.getHash() == null) {
// It is impossible to compute the hash of a parent if the child has a null hash
return;
}
}
recalculated = cryptography.digestSync(internal, Cryptography.DEFAULT_DIGEST_TYPE, false);
}
if (!old.equals(recalculated)) {
mismatchCallback.accept(node);
}
}
/**
* Get a list of all nodes in a tree that have an invalid hash.
* Returns an empty list if the entire tree has valid hashes.
*
* @param root
* the root of the tree in question
* @return a list of nodes with invalid hashes (if there are any)
*/
public static List getNodesWithInvalidHashes(final MerkleNode root) {
final List nodesWithInvalidHashes = new LinkedList<>();
findInvalidHashes(root, nodesWithInvalidHashes::add);
return nodesWithInvalidHashes;
}
/**
* Check if all of the hashes within a tree are valid.
* Write a detailed message to the log if invalid hashes are detected in the tree.
*
* @param root
* the root of the tree to check
* @param context
* the context that the check is being done in. This is written to the log if a problem is detected.
* @param limit
* the maximum number of invalid nodes to log. If the entire tree is invalid then the log could be massively
* spammed. A sane limit reduces the amount logged in that situation.
* @return true if the tree is valid, false if it is not valid
*/
public static boolean checkHashAndLog(final MerkleNode root, final String context, final int limit) {
final List nodesWithInvalidHashes = getNodesWithInvalidHashes(root);
if (nodesWithInvalidHashes.isEmpty()) {
return true;
}
final StringBuilder sb = new StringBuilder();
sb.append("Invalid merkle hashes detected in tree. Context = ")
.append(context)
.append("\n");
int nodesWithNullHash = 0;
int nodesWithInvalidHash = 0;
for (final MerkleNode node : nodesWithInvalidHashes) {
if (node.getHash() == null) {
nodesWithNullHash++;
} else {
nodesWithInvalidHash++;
}
}
sb.append(nodesWithNullHash)
.append(" ")
.append(plural(nodesWithNullHash, "node"))
.append(" ")
.append(plural(nodesWithNullHash, "has a null hash", "have null hashes"))
.append(". ");
sb.append(nodesWithInvalidHash)
.append(" ")
.append(plural(nodesWithInvalidHash, "node"))
.append(" ")
.append(plural(nodesWithInvalidHash, "has an invalid hash", "have invalid hashes"))
.append(".\n");
if (nodesWithInvalidHashes.size() > limit) {
sb.append("Number of nodes exceeds maximum limit, only logging the first ")
.append(limit)
.append(" ")
.append(plural(limit, "node"))
.append(".\n");
}
int count = 0;
for (final MerkleNode node : nodesWithInvalidHashes) {
count++;
if (count > limit) {
break;
}
sb.append(" - ")
.append(node.getClass().getSimpleName())
.append(" @ ")
.append(node.getRoute())
.append(" ");
if (node.getHash() == null) {
sb.append("has a null hash");
} else {
sb.append("has an invalid hash");
}
sb.append("\n");
}
logger.error(EXCEPTION.getMarker(), sb);
return false;
}
}