From f26b9ae2991f957bde68c9b5a1a4997520fdef6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96zg=C3=BCn=20Ozan=20Nacitarhan?= <72516041+onacitarhan17@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:14:06 +0300 Subject: [PATCH 1/5] Ozan/merkletree (#32) * implements merkle tree implementation and insertion to it * implements merkle tree authentication * implements tests * implements hash table for searching in merkle tree * implements thread safety to merkle tree implementation * fixes lint & spotbugs issues * renames Merkle Tree creation function in MerkleTreeFixture.java * suppresses EI_EXPOSE_REP warnings in spotbugs * suppresses warnings in spotbugs * adds comments for unit tests * handles exceptions on MerkleTree.java * changes how id of an entity is stored on the merkle tree, changed from hash(id) to id * handles exceptions better for locks * changes get method in a way it gets the AuthenticatedEntity by id not Entity * improves thread safety check * improves exception handling1 * improves the hashing and how proofs are created in merkle tree * applies revisions * applies revisions * applies revisions * applies revisions * fixes lint * removes unnecessary setters * removes unnecessary setters * fixes a java doc * applies revisions * fixes lint * applies revisions Co-authored-by: Yahya --- src/main/java/crypto/Sha3256Hasher.java | 51 +++- ...LightChainDistributedStorageException.java | 5 +- .../ads/AuthenticatedDataStructure.java | 2 + .../java/modules/ads/AuthenticatedEntity.java | 4 +- .../java/modules/ads/MembershipProof.java | 17 +- .../modules/ads/merkletree/MerkleNode.java | 117 ++++++++ .../modules/ads/merkletree/MerkleProof.java | 71 +++++ .../modules/ads/merkletree/MerkleTree.java | 136 +++++++++ .../MerkleTreeAuthenticatedEntity.java | 41 +++ ...MerkleTreeAuthenticatedEntityVerifier.java | 45 +++ .../java/modules/ads/skiplist/SkipList.java | 22 -- src/main/java/network/NetworkAdapter.java | 11 + src/test/java/modules/ads/MerkleTreeTest.java | 271 ++++++++++++++++++ src/test/java/modules/ads/SkipListTest.java | 18 -- src/test/java/networking/MockConduit.java | 6 +- src/test/java/networking/stub/Hub.java | 79 ++++- .../java/networking/stub/StubNetwork.java | 20 +- .../stub/StubNetworkStorageTest.java | 150 ++++++++++ .../unittest/fixtures/MerkleTreeFixture.java | 22 ++ .../unittest/fixtures/Sha3256HashFixture.java | 16 ++ 20 files changed, 1044 insertions(+), 60 deletions(-) create mode 100644 src/main/java/modules/ads/merkletree/MerkleNode.java create mode 100644 src/main/java/modules/ads/merkletree/MerkleProof.java create mode 100644 src/main/java/modules/ads/merkletree/MerkleTree.java create mode 100644 src/main/java/modules/ads/merkletree/MerkleTreeAuthenticatedEntity.java create mode 100644 src/main/java/modules/ads/merkletree/MerkleTreeAuthenticatedEntityVerifier.java delete mode 100644 src/main/java/modules/ads/skiplist/SkipList.java create mode 100644 src/test/java/modules/ads/MerkleTreeTest.java delete mode 100644 src/test/java/modules/ads/SkipListTest.java create mode 100644 src/test/java/unittest/fixtures/MerkleTreeFixture.java diff --git a/src/main/java/crypto/Sha3256Hasher.java b/src/main/java/crypto/Sha3256Hasher.java index 4a954990..3f50e4ff 100644 --- a/src/main/java/crypto/Sha3256Hasher.java +++ b/src/main/java/crypto/Sha3256Hasher.java @@ -5,14 +5,21 @@ import model.codec.EncodedEntity; import model.crypto.Sha3256Hash; +import model.lightchain.Identifier; /** * Implements SHA3-256 hashing functionality. */ public class Sha3256Hasher implements Hasher { - private static final String HASH_ALG_SHA_3_256 = "SHA3-256"; + private static byte[] concat(final byte[] e1, final byte[] e2) { + byte[] result = new byte[e1.length + e2.length]; + System.arraycopy(e1, 0, result, 0, e1.length); + System.arraycopy(e2, 0, result, e1.length, e2.length); + return result; + } + /** * Computes hash of the given encoded entity. * @@ -21,12 +28,52 @@ public class Sha3256Hasher implements Hasher { */ @Override public Sha3256Hash computeHash(EncodedEntity e) { + return this.computeHash(e.getBytes()); + } + + /** + * Computes hash of the given identifier. + * + * @param id input identifier + * @return SHA3-256 hash object of the entity + */ + public Sha3256Hash computeHash(Identifier id) { + return this.computeHash(id.getBytes()); + } + + /** + * Computes hash of the given bytes. + * + * @param bytes input bytes. + * @return SHA3-256 hash object of the given bytes. + */ + public Sha3256Hash computeHash(byte[] bytes) { try { MessageDigest md = MessageDigest.getInstance(HASH_ALG_SHA_3_256); - byte[] hashValue = md.digest(e.getBytes()); + byte[] hashValue = md.digest(bytes); return new Sha3256Hash(hashValue); } catch (NoSuchAlgorithmException ex) { throw new IllegalStateException(HASH_ALG_SHA_3_256 + "algorithm not found.", ex); } } + + /** + * Commutative hashing of two given byte arrays. + * + * @param b1 first byte array. + * @param b2 second byte array. + * @return SHA3-256 hash object of the commutative concatenation of the two byte arrays. + */ + public Sha3256Hash computeHash(byte[] b1, byte[] b2) { + try { + MessageDigest md = MessageDigest.getInstance(HASH_ALG_SHA_3_256); + return new Sha3256Hash(md.digest(concat(b1, b2))); + } catch (NoSuchAlgorithmException ex) { + throw new IllegalStateException(HASH_ALG_SHA_3_256 + "algorithm not found.", ex); + } + } + + public Sha3256Hash computeHash(Sha3256Hash h1, Sha3256Hash h2) { + return computeHash(h1.getBytes(), h2.getBytes()); + } } diff --git a/src/main/java/model/exceptions/LightChainDistributedStorageException.java b/src/main/java/model/exceptions/LightChainDistributedStorageException.java index 5e5713a6..ee4a2a98 100644 --- a/src/main/java/model/exceptions/LightChainDistributedStorageException.java +++ b/src/main/java/model/exceptions/LightChainDistributedStorageException.java @@ -3,4 +3,7 @@ /** * Represents a runtime exception happens on distributed storage layer of LightChain. */ -public class LightChainDistributedStorageException extends Exception{} +public class LightChainDistributedStorageException extends Exception{ + public LightChainDistributedStorageException(String s) { + } +} diff --git a/src/main/java/modules/ads/AuthenticatedDataStructure.java b/src/main/java/modules/ads/AuthenticatedDataStructure.java index 8fde6250..8b360017 100644 --- a/src/main/java/modules/ads/AuthenticatedDataStructure.java +++ b/src/main/java/modules/ads/AuthenticatedDataStructure.java @@ -10,4 +10,6 @@ public interface AuthenticatedDataStructure { AuthenticatedEntity put(Entity e); AuthenticatedEntity get(Identifier id); + + int size(); } diff --git a/src/main/java/modules/ads/AuthenticatedEntity.java b/src/main/java/modules/ads/AuthenticatedEntity.java index d66d35a8..449aa167 100644 --- a/src/main/java/modules/ads/AuthenticatedEntity.java +++ b/src/main/java/modules/ads/AuthenticatedEntity.java @@ -7,7 +7,7 @@ * that entity against a root identifier. */ public abstract class AuthenticatedEntity extends Entity { - abstract Entity getEntity(); + public abstract Entity getEntity(); - abstract MembershipProof getMembershipProof(); + public abstract MembershipProof getMembershipProof(); } diff --git a/src/main/java/modules/ads/MembershipProof.java b/src/main/java/modules/ads/MembershipProof.java index 50078bd9..724792ff 100644 --- a/src/main/java/modules/ads/MembershipProof.java +++ b/src/main/java/modules/ads/MembershipProof.java @@ -1,6 +1,8 @@ package modules.ads; -import model.lightchain.Identifier; +import java.util.ArrayList; + +import model.crypto.Sha3256Hash; /** * Represents a Merkle Proof of membership against a certain root identifier. @@ -9,15 +11,16 @@ public interface MembershipProof { /** * Root of the authenticated data structure that this proof belongs to. * - * @return root identifier. + * @return hash value of the root node. */ - Identifier getRoot(); + Sha3256Hash getRoot(); /** - * Sibling of the given identifier on the membership Merkle Proof path to the root. + * Returns the path of the proof of membership. * - * @param identifier identifier of the entity. - * @return sibling of given identifier. + * @return path of the proof of membership. */ - Identifier getSiblingOf(Identifier identifier); + ArrayList getPath(); + + ArrayList getIsLeftNode(); } diff --git a/src/main/java/modules/ads/merkletree/MerkleNode.java b/src/main/java/modules/ads/merkletree/MerkleNode.java new file mode 100644 index 00000000..4551b1bf --- /dev/null +++ b/src/main/java/modules/ads/merkletree/MerkleNode.java @@ -0,0 +1,117 @@ +package modules.ads.merkletree; + +import crypto.Sha3256Hasher; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import model.Entity; +import model.crypto.Sha3256Hash; + +/** + * A node in the Merkle tree. + */ +public class MerkleNode { + private static final Sha3256Hasher hasher = new Sha3256Hasher(); + private MerkleNode left; + private MerkleNode right; + private MerkleNode parent; + private boolean isLeft; + private Sha3256Hash hash; + + /** + * Default constructor. + */ + public MerkleNode() { + this.left = null; + this.right = null; + this.parent = null; + this.isLeft = false; + this.hash = null; + } + + /** + * Constructor with entity and isLeft. + * + * @param e input entity + * @param isLeft boolean that specifies if the node is left child or not + */ + public MerkleNode(Entity e, boolean isLeft) { + this.left = null; + this.right = null; + this.parent = null; + this.isLeft = isLeft; + this.hash = hasher.computeHash(e.id()); + } + + /** + * Constructor with hash of the entity. + * + * @param hash input hash of the entity corresponding to that node + */ + public MerkleNode(Sha3256Hash hash) { + this.left = null; + this.right = null; + this.parent = null; + this.isLeft = false; + this.hash = hash; + } + + /** + * Constructor of a parent node. + * + * @param hash input hash of the entity corresponding to that node + * @param left left child of the node + * @param right right child of the node + */ + @SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "left and right are intentionally mutable externally") + public MerkleNode(Sha3256Hash hash, MerkleNode left, MerkleNode right) { + this.left = left; + this.right = right; + this.parent = null; + this.isLeft = false; + this.hash = hash; + } + + @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "internal representation is intentionally returned") + public MerkleNode getLeft() { + return left; + } + + @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "internal representation is intentionally returned") + public MerkleNode getRight() { + return right; + } + + @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "internal representation is intentionally returned") + public MerkleNode getParent() { + return parent; + } + + @SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "parent is intentionally mutable externally") + public void setParent(MerkleNode parent) { + this.parent = parent; + } + + public Sha3256Hash getHash() { + return hash; + } + + public boolean isLeft() { + return isLeft; + } + + public void setLeft(boolean isLeft) { + this.isLeft = isLeft; + } + + /** + * Returns the sibling of the node. + * + * @return the sibling of the node + */ + public MerkleNode getSibling() { + if (isLeft()) { + return parent.getRight(); + } else { + return parent.getLeft(); + } + } +} diff --git a/src/main/java/modules/ads/merkletree/MerkleProof.java b/src/main/java/modules/ads/merkletree/MerkleProof.java new file mode 100644 index 00000000..c0b3e8c4 --- /dev/null +++ b/src/main/java/modules/ads/merkletree/MerkleProof.java @@ -0,0 +1,71 @@ +package modules.ads.merkletree; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Objects; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import model.crypto.Sha3256Hash; +import modules.ads.MembershipProof; + +/** + * A proof of membership in a Merkle tree. + */ +public class MerkleProof implements MembershipProof { + private ArrayList path; + private final ArrayList isLeftNode; + private final Sha3256Hash root; + + /** + * Constructs a proof from a list of hashes and a root. + * + * @param path the list of hashes + * @param root the root + * @param isLeftNode the list of isLeft Boolean values of the hashes + */ + public MerkleProof(ArrayList path, Sha3256Hash root, ArrayList isLeftNode) { + this.path = new ArrayList<>(path); + this.root = root; + this.isLeftNode = new ArrayList<>(isLeftNode); + } + + @Override + public ArrayList getPath() { + return new ArrayList<>(path); + } + + public void setPath(ArrayList path) { + this.path = new ArrayList<>(path); + } + + @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "internal representation is intentionally returned") + public ArrayList getIsLeftNode() { + return isLeftNode; + } + + public Sha3256Hash getRoot() { + return root; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MerkleProof proof = (MerkleProof) o; + for (int i = 0; i < path.size(); i++) { + if (!Arrays.equals(path.get(i).getBytes(), proof.path.get(i).getBytes())) { + return false; + } + } + return root.equals(proof.root); + } + + @Override + public int hashCode() { + return Objects.hash(path, root); + } +} diff --git a/src/main/java/modules/ads/merkletree/MerkleTree.java b/src/main/java/modules/ads/merkletree/MerkleTree.java new file mode 100644 index 00000000..2c194c4a --- /dev/null +++ b/src/main/java/modules/ads/merkletree/MerkleTree.java @@ -0,0 +1,136 @@ +package modules.ads.merkletree; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import crypto.Sha3256Hasher; +import model.Entity; +import model.crypto.Sha3256Hash; +import model.lightchain.Identifier; +import modules.ads.AuthenticatedDataStructure; + +/** + * Implementation of an in-memory Authenticated Skip List + * that is capable of storing and retrieval of LightChain entities. + */ +public class MerkleTree implements AuthenticatedDataStructure { + private static final Sha3256Hasher hasher = new Sha3256Hasher(); + private final ReentrantReadWriteLock lock; + private final ArrayList leafNodes; + private final Map leafNodesHashTable; + private final Map entityHashTable; + private int size; + private MerkleNode root; + + /** + * Default constructor for a Merkle Tree. + */ + public MerkleTree() { + this.size = 0; + this.root = new MerkleNode(); + this.leafNodes = new ArrayList<>(); + this.lock = new ReentrantReadWriteLock(); + this.leafNodesHashTable = new HashMap<>(); + this.entityHashTable = new HashMap<>(); + } + + @Override + public modules.ads.AuthenticatedEntity put(Entity e) throws IllegalArgumentException { + try { + lock.writeLock().lock(); + if (e == null) { + throw new IllegalArgumentException("entity cannot be null"); + } + Sha3256Hash hash = new Sha3256Hash(e.id().getBytes()); + Integer idx = leafNodesHashTable.get(hash); + if (idx == null) { + leafNodes.add(new MerkleNode(e, false)); + leafNodesHashTable.put(hash, size); + entityHashTable.put(e.id(), e); + size++; + buildMerkleTree(); + MerkleProof proof = getProof(e.id()); + return new MerkleTreeAuthenticatedEntity(proof, e.type(), e); + } else { + MerkleProof proof = getProof(e.id()); + return new MerkleTreeAuthenticatedEntity(proof, e.type(), e); + } + } finally { + lock.writeLock().unlock(); + } + } + + @Override + public modules.ads.AuthenticatedEntity get(Identifier id) throws IllegalArgumentException { + MerkleProof proof; + if (id == null) { + throw new IllegalArgumentException("identifier cannot be null"); + } + try { + lock.readLock().lock(); + proof = getProof(id); + Entity e = entityHashTable.get(id); + return new MerkleTreeAuthenticatedEntity(proof, e.type(), e); + } finally { + lock.readLock().unlock(); + } + } + + private MerkleProof getProof(Identifier id) throws IllegalArgumentException { + ArrayList isLeftNode = new ArrayList<>(); + Sha3256Hash hash = new Sha3256Hash(id.getBytes()); + Integer idx = leafNodesHashTable.get(hash); + if (idx == null) { + throw new IllegalArgumentException("identifier not found"); + } + ArrayList path = new ArrayList<>(); + MerkleNode currentNode = leafNodes.get(idx); + while (currentNode != root) { + path.add(currentNode.getSibling().getHash()); + isLeftNode.add(currentNode.isLeft()); + currentNode = currentNode.getParent(); + } + return new MerkleProof(path, root.getHash(), isLeftNode); + } + + private void buildMerkleTree() { + // keeps nodes of the current level of the merkle tree + // will be updated bottom up + // initialized with leaves + ArrayList currentLevelNodes = new ArrayList<>(leafNodes); + + // keeps nodes of the next level of merkle tree + // used as an intermediary data structure. + ArrayList nextLevelNodes = new ArrayList<>(); + + while (currentLevelNodes.size() > 1) { // more than one current node, means we have not yet reached root. + for (int i = 0; i < currentLevelNodes.size(); i += 2) { + // pairs up current level nodes as siblings for next level. + MerkleNode left = currentLevelNodes.get(i); + left.setLeft(true); + + MerkleNode right; + if (i + 1 < currentLevelNodes.size()) { + right = currentLevelNodes.get(i + 1); // we have a right node + } else { + // TODO: edge case need to get fixed. + right = new MerkleNode(left.getHash()); + } + Sha3256Hash hash = hasher.computeHash(left.getHash().getBytes(), right.getHash().getBytes()); + MerkleNode parent = new MerkleNode(hash, left, right); + left.setParent(parent); + right.setParent(parent); + nextLevelNodes.add(parent); + } + currentLevelNodes = nextLevelNodes; + nextLevelNodes = new ArrayList<>(); + } + root = currentLevelNodes.get(0); + } + + public int size() { + return this.size; + } +} diff --git a/src/main/java/modules/ads/merkletree/MerkleTreeAuthenticatedEntity.java b/src/main/java/modules/ads/merkletree/MerkleTreeAuthenticatedEntity.java new file mode 100644 index 00000000..a6aa2f33 --- /dev/null +++ b/src/main/java/modules/ads/merkletree/MerkleTreeAuthenticatedEntity.java @@ -0,0 +1,41 @@ +package modules.ads.merkletree; + +import model.Entity; +import modules.ads.MembershipProof; + +/** + * An entity with its membership proof and type. + */ +public class MerkleTreeAuthenticatedEntity extends modules.ads.AuthenticatedEntity { + private final MembershipProof membershipProof; + private final String type; + private final Entity entity; + + /** + * Constructor of an authenticated entity. + * + * @param proof the membership proof + * @param type the type of the entity + * @param e the entity + */ + public MerkleTreeAuthenticatedEntity(MerkleProof proof, String type, Entity e) { + this.membershipProof = new MerkleProof(proof.getPath(), proof.getRoot(), proof.getIsLeftNode()); + this.type = type; + this.entity = e; + } + + @Override + public String type() { + return type; + } + + @Override + public Entity getEntity() { + return entity; + } + + @Override + public MembershipProof getMembershipProof() { + return new MerkleProof(membershipProof.getPath(), membershipProof.getRoot(), membershipProof.getIsLeftNode()); + } +} diff --git a/src/main/java/modules/ads/merkletree/MerkleTreeAuthenticatedEntityVerifier.java b/src/main/java/modules/ads/merkletree/MerkleTreeAuthenticatedEntityVerifier.java new file mode 100644 index 00000000..bdea1990 --- /dev/null +++ b/src/main/java/modules/ads/merkletree/MerkleTreeAuthenticatedEntityVerifier.java @@ -0,0 +1,45 @@ +package modules.ads.merkletree; + +import java.util.ArrayList; +import java.util.Arrays; + +import crypto.Sha3256Hasher; +import model.crypto.Sha3256Hash; +import modules.ads.AuthenticatedEntity; +import modules.ads.MembershipProof; + +/** + * Verifies the AuthenticatedEntity against its self-contained proof. + */ +public class MerkleTreeAuthenticatedEntityVerifier implements modules.ads.AuthenticatedEntityVerifier { + + /** + * Verifies the AuthenticatedEntity against its self-contained proof. + * + * @param authenticatedEntity the AuthenticatedEntity to verify. + * @return true if entity contains a valid Merkle Proof against its root identifier, false otherwise. + */ + @Override + public boolean verify(AuthenticatedEntity authenticatedEntity) { + Sha3256Hasher hasher = new Sha3256Hasher(); + MembershipProof proof = authenticatedEntity.getMembershipProof(); + ArrayList isLeftNode = proof.getIsLeftNode(); + ArrayList proofPath = proof.getPath(); + + Sha3256Hash initialHash = hasher.computeHash(authenticatedEntity.getEntity().id()); + Sha3256Hash currentHash; + if (isLeftNode.get(0)) { + currentHash = hasher.computeHash(initialHash, proofPath.get(0)); + } else { + currentHash = hasher.computeHash(proofPath.get(0), initialHash); + } + for (int i = 1; i < proofPath.size(); i++) { + if (isLeftNode.get(i)) { + currentHash = hasher.computeHash(currentHash, proofPath.get(i)); + } else { + currentHash = hasher.computeHash(proofPath.get(i), currentHash); + } + } + return Arrays.equals(proof.getRoot().getBytes(), currentHash.getBytes()); + } +} \ No newline at end of file diff --git a/src/main/java/modules/ads/skiplist/SkipList.java b/src/main/java/modules/ads/skiplist/SkipList.java deleted file mode 100644 index 6a77ecdb..00000000 --- a/src/main/java/modules/ads/skiplist/SkipList.java +++ /dev/null @@ -1,22 +0,0 @@ -package modules.ads.skiplist; - -import model.Entity; -import model.lightchain.Identifier; -import modules.ads.AuthenticatedDataStructure; -import modules.ads.AuthenticatedEntity; - -/** - * Implementation of an in-memory Authenticated Skip List - * that is capable of storing and retrieval of LightChain entities. - */ -public class SkipList implements AuthenticatedDataStructure { - @Override - public AuthenticatedEntity put(Entity e) { - return null; - } - - @Override - public AuthenticatedEntity get(Identifier id) { - return null; - } -} diff --git a/src/main/java/network/NetworkAdapter.java b/src/main/java/network/NetworkAdapter.java index e5151104..14e46911 100644 --- a/src/main/java/network/NetworkAdapter.java +++ b/src/main/java/network/NetworkAdapter.java @@ -5,6 +5,8 @@ import model.exceptions.LightChainNetworkingException; import model.lightchain.Identifier; +import java.util.ArrayList; + /** * NetworkAdapter models the interface that is exposed to the conduits from the networking layer. */ @@ -38,4 +40,13 @@ public interface NetworkAdapter { * @throws LightChainDistributedStorageException any unhappy path taken on retrieving the Entity. */ Entity get(Identifier identifier, String namespace) throws LightChainDistributedStorageException; + + /** + * Retrieves all entities stored on the underlying DHT of nodes that stored on this channel. + * + * @param namespace the namespace on which this query is resolved. + * @return list of all entities stored on this channel from underlying DHT. + * @throws LightChainDistributedStorageException any unhappy path taken on retrieving the Entities. + */ + ArrayList allEntities(String namespace) throws LightChainDistributedStorageException; } diff --git a/src/test/java/modules/ads/MerkleTreeTest.java b/src/test/java/modules/ads/MerkleTreeTest.java new file mode 100644 index 00000000..95a290dc --- /dev/null +++ b/src/test/java/modules/ads/MerkleTreeTest.java @@ -0,0 +1,271 @@ +package modules.ads; + +import java.util.ArrayList; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import model.Entity; +import model.crypto.Sha3256Hash; +import model.lightchain.Identifier; +import modules.ads.merkletree.MerkleProof; +import modules.ads.merkletree.MerkleTree; +import modules.ads.merkletree.MerkleTreeAuthenticatedEntity; +import modules.ads.merkletree.MerkleTreeAuthenticatedEntityVerifier; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import unittest.fixtures.EntityFixture; +import unittest.fixtures.MerkleTreeFixture; +import unittest.fixtures.Sha3256HashFixture; + +/** + * Encapsulates tests for an authenticated and concurrent implementation of MerkleTree ADS. + */ +public class MerkleTreeTest { + + /** + * A basic test for one sequential put and get operations. + */ + @Test + public void testVerification() { + MerkleTree merkleTree = MerkleTreeFixture.createMerkleTree(5); + Assertions.assertEquals(merkleTree.size(), 5); // fixture sanity check. + + Entity entity = new EntityFixture(); + merkleTree.put(entity); + Assertions.assertEquals(merkleTree.size(), 6); + + AuthenticatedEntity authenticatedEntity = merkleTree.get(entity.id()); + MerkleTreeAuthenticatedEntityVerifier verifier = new MerkleTreeAuthenticatedEntityVerifier(); + Assertions.assertTrue(verifier.verify(authenticatedEntity)); + } + + /** + * Tests both putting and getting the same entity gives same proof + * and putting another entity gives different proofs. + */ + @Test + public void testPutGetSameProof() { + MerkleTree merkleTree = MerkleTreeFixture.createMerkleTree(5); + Assertions.assertEquals(merkleTree.size(), 5); // fixture sanity check. + Entity e1 = new EntityFixture(); + + // putting e1 + AuthenticatedEntity authenticatedEntityPut = merkleTree.put(e1); + MembershipProof proofPutE1 = authenticatedEntityPut.getMembershipProof(); + Assertions.assertEquals(merkleTree.size(), 6); + + // getting e1 + AuthenticatedEntity authE1Get = merkleTree.get(e1.id()); + MembershipProof proofGetE1 = authE1Get.getMembershipProof(); + + // putting e2 + Entity e2 = new EntityFixture(); + AuthenticatedEntity authE2Put = merkleTree.put(e2); + Assertions.assertEquals(merkleTree.size(), 7); + + // getting e2 + MembershipProof proofPutE2 = authE2Put.getMembershipProof(); + + // proofs for putting and getting e1 should be the same. + Assertions.assertEquals(proofPutE1, proofGetE1); + + // proofs for putting e1 and e2 must be different. + Assertions.assertNotEquals(proofPutE1, proofPutE2); + } + + /** + * Tests putting an existing entity does not change the proof. + */ + @Test + public void testPutExistingEntity() { + MerkleTree merkleTree = MerkleTreeFixture.createMerkleTree(5); + Assertions.assertEquals(merkleTree.size(), 5); // fixture sanity check. + Entity entity = new EntityFixture(); + + // first time put + AuthenticatedEntity authenticatedEntityPut = merkleTree.put(entity); + MembershipProof proofPut = authenticatedEntityPut.getMembershipProof(); + Assertions.assertEquals(merkleTree.size(), 6); + + // second attempt + AuthenticatedEntity authenticatedEntityPutAgain = merkleTree.put(entity); + MembershipProof proofPutAgain = authenticatedEntityPutAgain.getMembershipProof(); + + // proofs must be equal. + Assertions.assertEquals(proofPut, proofPutAgain); + Assertions.assertEquals(merkleTree.size(), 6); // duplicate entity should not change the size. + } + + /** + * Concurrently puts and gets entities and checks their proofs are correct (thread safety check). + */ + @Test + public void testConcurrentPutGet() { + int concurrencyDegree = 100; + ArrayList entities = new ArrayList<>(); + ArrayList ids = new ArrayList<>(); + + AtomicInteger threadError = new AtomicInteger(); + CountDownLatch putDone = new CountDownLatch(concurrencyDegree); + CountDownLatch getDone = new CountDownLatch(concurrencyDegree); + + Thread[] putThreads = new Thread[concurrencyDegree]; + Thread[] getThreads = new Thread[concurrencyDegree]; + + MerkleTree merkleTree = MerkleTreeFixture.createMerkleTree(0); + Assertions.assertEquals(merkleTree.size(), 0); // fixture sanity check. + + for (int i = 0; i < concurrencyDegree; i++) { + Entity entity = new EntityFixture(); + entities.add(entity); + ids.add(entity.id()); + } + + // put + for (int i = 0; i < concurrencyDegree; i++) { + Entity entity = entities.get(i); + putThreads[i] = new Thread(() -> { + try { + merkleTree.put(entity); + putDone.countDown(); + } catch (Exception e) { + threadError.getAndIncrement(); + } + }); + } + for (Thread t : putThreads) { + t.start(); + } + try { + boolean doneOneTimePut = putDone.await(60, TimeUnit.SECONDS); + Assertions.assertTrue(doneOneTimePut); + } catch (InterruptedException e) { + Assertions.fail(); + } + + // get + for (int i = 0; i < concurrencyDegree; i++) { + Identifier id = ids.get(i); + getThreads[i] = new Thread(() -> { + try { + AuthenticatedEntity authenticatedEntity = merkleTree.get(id); + MerkleTreeAuthenticatedEntityVerifier verifier = new MerkleTreeAuthenticatedEntityVerifier(); + if (!verifier.verify(authenticatedEntity)) { + threadError.getAndIncrement(); + } + getDone.countDown(); + } catch (Exception e) { + threadError.getAndIncrement(); + } + }); + } + for (Thread t : getThreads) { + t.start(); + } + try { + boolean doneOneTimeGet = getDone.await(60, TimeUnit.SECONDS); + Assertions.assertTrue(doneOneTimeGet); + } catch (InterruptedException e) { + Assertions.fail(); + } + Assertions.assertEquals(0, threadError.get()); + Assertions.assertEquals(concurrencyDegree, merkleTree.size()); + } + + /** + * Tests getting an entity that does not exist in the merkle tree throws IllegalArgumentException. + */ + @Test + public void testGetNonExistingEntity() { + MerkleTree merkleTree = MerkleTreeFixture.createMerkleTree(5); + Assertions.assertEquals(merkleTree.size(), 5); // fixture sanity check. + Entity entity = new EntityFixture(); + + Assertions.assertThrows(IllegalArgumentException.class, () -> { + merkleTree.get(entity.id()); + }); + } + + /** + * Tests inserting null throws IllegalArgumentException. + */ + @Test + public void testNullInsertion() { + MerkleTree merkleTree = MerkleTreeFixture.createMerkleTree(5); + Assertions.assertEquals(merkleTree.size(), 5); // fixture sanity check. + Assertions.assertThrows(IllegalArgumentException.class, () -> { + merkleTree.put(null); + }); + } + + /** + * Tests the proof verification fails when root is changed. + */ + @Test + public void testManipulatedRoot() { + MerkleTree merkleTree = MerkleTreeFixture.createMerkleTree(5); + Assertions.assertEquals(merkleTree.size(), 5); // fixture sanity check. + Entity entity = new EntityFixture(); + AuthenticatedEntity authenticatedEntity = merkleTree.put(entity); + MembershipProof proof = authenticatedEntity.getMembershipProof(); + + // creates a tampered proof with random root. + MerkleProof tamperedProof = new MerkleProof(proof.getPath(), new Sha3256Hash(new byte[32]), proof.getIsLeftNode()); + AuthenticatedEntity tamperedAuthenticatedEntity = new MerkleTreeAuthenticatedEntity( + tamperedProof, + authenticatedEntity.type(), + authenticatedEntity.getEntity()); + + MerkleTreeAuthenticatedEntityVerifier verifier = new MerkleTreeAuthenticatedEntityVerifier(); + // authenticated entity must be verified. + Assertions.assertTrue(verifier.verify(authenticatedEntity)); + // tampered authenticated entity must be failed. + Assertions.assertFalse(verifier.verify(tamperedAuthenticatedEntity)); + } + + /** + * Tests the proof verification fails when entity is changed. + */ + @Test + public void testManipulatedEntity() { + MerkleTree merkleTree = MerkleTreeFixture.createMerkleTree(5); + Assertions.assertEquals(merkleTree.size(), 5); // fixture sanity check. + Entity entity = new EntityFixture(); + AuthenticatedEntity authenticatedEntity = merkleTree.put(entity); + + AuthenticatedEntity tamperedEntity = new MerkleTreeAuthenticatedEntity( + (MerkleProof) authenticatedEntity.getMembershipProof(), + authenticatedEntity.type(), + new EntityFixture()); + + MerkleTreeAuthenticatedEntityVerifier verifier = new MerkleTreeAuthenticatedEntityVerifier(); + Assertions.assertTrue(verifier.verify(authenticatedEntity)); // original authenticated entity passes verification. + Assertions.assertFalse(verifier.verify(tamperedEntity)); // tampered entity fails verification. + } + + /** + * Tests the proof fails verification when proof part of authenticated entity is changed. + */ + @Test + public void testManipulatedProof() { + MerkleTree merkleTree = MerkleTreeFixture.createMerkleTree(5); + Assertions.assertEquals(merkleTree.size(), 5); // fixture sanity check. + + Entity entity = new EntityFixture(); + AuthenticatedEntity authenticatedEntity = merkleTree.put(entity); + MembershipProof proof = authenticatedEntity.getMembershipProof(); + + AuthenticatedEntity tamperedEntity = new MerkleTreeAuthenticatedEntity( + new MerkleProof(Sha3256HashFixture.newSha3256HashArrayList( + proof.getPath().size()), + proof.getRoot(), + proof.getIsLeftNode()), + authenticatedEntity.type(), + authenticatedEntity.getEntity()); + + MerkleTreeAuthenticatedEntityVerifier verifier = new MerkleTreeAuthenticatedEntityVerifier(); + Assertions.assertTrue(verifier.verify(authenticatedEntity)); // original authenticated entity passes verification. + Assertions.assertFalse(verifier.verify(tamperedEntity)); // tampered entity fails verification. + } +} diff --git a/src/test/java/modules/ads/SkipListTest.java b/src/test/java/modules/ads/SkipListTest.java deleted file mode 100644 index 7b48990a..00000000 --- a/src/test/java/modules/ads/SkipListTest.java +++ /dev/null @@ -1,18 +0,0 @@ -package modules.ads; - -/** - * Encapsulates tests for an authenticated and concurrent implementation of SkipList ADS. - */ -public class SkipListTest { - // TODO: writing tests to cover - // 1. When putting a unique entity into skip list, we can recover it. - // 2. Proof of membership for putting and getting an entity is the same. - // 3. Putting an already existing entity does not change its membership proof. - // 4. Putting 100 distinct entities concurrently inserts all of them into skip list with correct membership proofs, - // and also, makes them all retrievable with correct membership proofs. - // 5. Getting non-existing identifiers returns null. - // 7. Putting null returns null. - // 8. Tampering with root identifier of an authenticated entity fails its verification. - // 9. Tampering with entity of an authenticated entity fails its verification. - // 10. Tampering with proof of an authenticated entity fails its verification. -} diff --git a/src/test/java/networking/MockConduit.java b/src/test/java/networking/MockConduit.java index 86f2191e..a135ba65 100644 --- a/src/test/java/networking/MockConduit.java +++ b/src/test/java/networking/MockConduit.java @@ -53,7 +53,7 @@ public void unicast(Entity e, Identifier target) throws LightChainNetworkingExce */ @Override public void put(Entity e) throws LightChainDistributedStorageException { - + this.networkAdapter.put(e, channel); } /** @@ -66,12 +66,12 @@ public void put(Entity e) throws LightChainDistributedStorageException { */ @Override public Entity get(Identifier identifier) throws LightChainDistributedStorageException { - return null; + return this.networkAdapter.get(identifier, channel); } @Override public ArrayList allEntities() throws LightChainDistributedStorageException { - return null; + return this.networkAdapter.allEntities(channel); } public boolean hasSent(Identifier entityId) { diff --git a/src/test/java/networking/stub/Hub.java b/src/test/java/networking/stub/Hub.java index 3a53543c..aea2f143 100644 --- a/src/test/java/networking/stub/Hub.java +++ b/src/test/java/networking/stub/Hub.java @@ -1,8 +1,11 @@ package networking.stub; +import java.util.ArrayList; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.ReentrantReadWriteLock; import model.Entity; +import model.exceptions.LightChainDistributedStorageException; import model.lightchain.Identifier; import network.Network; @@ -10,20 +13,90 @@ * Models the core communication part of the networking layer that allows stub network instances to talk to each other. */ public class Hub { + private final ReentrantReadWriteLock lock; + private final String channel1 = "test-network-channel-1"; + private final String channel2 = "test-network-channel-2"; private final ConcurrentHashMap networks; + private final ConcurrentHashMap channelMap1; + private final ConcurrentHashMap channelMap2; /** * Create a hub. */ public Hub() { this.networks = new ConcurrentHashMap<>(); + this.channelMap1 = new ConcurrentHashMap<>(); + this.channelMap2 = new ConcurrentHashMap<>(); + this.lock = new ReentrantReadWriteLock(); + } + + /** + * Put Entity to channel. + * + * @param e entitiy. + * @param namespace channel name. + * @throws LightChainDistributedStorageException any unhappy path taken on storing the Entity. + */ + public void putEntityToChannel(Entity e, String namespace) throws LightChainDistributedStorageException { + try { + lock.writeLock().lock(); + if (namespace.equals(channel1)) { + channelMap1.put(e.id(), e); + } else if (namespace.equals(channel2)) { + channelMap2.put(e.id(), e); + } else { + throw new LightChainDistributedStorageException("entity could not be put the given channel"); + } + } finally { + lock.writeLock().unlock(); + } + } + + /** + * Get entity from channel. + * + * @param identifier of entity. + * @param namespace channel name. + * @return entity. + * @throws LightChainDistributedStorageException any unhappy path taken on storing the Entity. + */ + public Entity getEntityFromChannel(Identifier identifier, String namespace) throws LightChainDistributedStorageException { + try { + lock.readLock().lock(); + if (namespace.equals(channel1)) { + return channelMap1.get(identifier); + } else if (namespace.equals(channel2)) { + return channelMap2.get(identifier); + } else { + throw new LightChainDistributedStorageException("could not get the entity"); + } + } finally { + lock.readLock().unlock(); + } + } + + /** + * Retrieves all entities stored on the underlying DHT of nodes that stored on this channel. + * + * @param namespace the namespace on which this query is resolved. + * @return list of all entities stored on this channel from underlying DHT. + * @throws LightChainDistributedStorageException any unhappy path taken on retrieving the Entities. + */ + public ArrayList getAllEntities(String namespace) throws LightChainDistributedStorageException { + if (namespace.equals(channel1)) { + return new ArrayList<>(channelMap1.values()); + } else if (namespace.equals(channel2)) { + return new ArrayList<>(channelMap2.values()); + } else { + throw new LightChainDistributedStorageException("could not get the entities"); + } } /** * Registeration of a network to the Hub. * * @param identifier identifier of network. - * @param network to be registered. + * @param network to be registered. */ public void registerNetwork(Identifier identifier, Network network) { networks.put(identifier, network); @@ -32,8 +105,8 @@ public void registerNetwork(Identifier identifier, Network network) { /** * Transfer entity from to another network on the same channel. * - * @param entity entity to be transferred. - * @param target identifier of target. + * @param entity entity to be transferred. + * @param target identifier of target. * @param channel channel on which the entity is delivered to target. */ public void transferEntity(Entity entity, Identifier target, String channel) throws IllegalStateException { diff --git a/src/test/java/networking/stub/StubNetwork.java b/src/test/java/networking/stub/StubNetwork.java index 7eed4979..5e152423 100644 --- a/src/test/java/networking/stub/StubNetwork.java +++ b/src/test/java/networking/stub/StubNetwork.java @@ -1,6 +1,8 @@ package networking.stub; +import java.util.ArrayList; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.ReentrantReadWriteLock; import model.Entity; import model.exceptions.LightChainDistributedStorageException; @@ -17,6 +19,8 @@ * A mock implementation of networking layer as a test util. */ public class StubNetwork implements Network, NetworkAdapter { + private final String channel1 = "test-network-channel-1"; + private final String channel2 = "test-network-channel-2"; private final ConcurrentHashMap engines; private final Hub hub; private final Identifier identifier; @@ -110,7 +114,7 @@ public void unicast(Entity e, Identifier target, String channel) throws LightCha */ @Override public void put(Entity e, String namespace) throws LightChainDistributedStorageException { - + this.hub.putEntityToChannel(e, namespace); } /** @@ -124,6 +128,18 @@ public void put(Entity e, String namespace) throws LightChainDistributedStorageE */ @Override public Entity get(Identifier identifier, String namespace) throws LightChainDistributedStorageException { - return null; + return this.hub.getEntityFromChannel(identifier, namespace); + } + + /** + * Retrieves all entities stored on the underlying DHT of nodes that stored on this channel. + * + * @param namespace the namespace on which this query is resolved. + * @return list of all entities stored on this channel from underlying DHT. + * @throws LightChainDistributedStorageException any unhappy path taken on retrieving the Entities. + */ + @Override + public ArrayList allEntities(String namespace) throws LightChainDistributedStorageException { + return this.hub.getAllEntities(namespace); } } \ No newline at end of file diff --git a/src/test/java/networking/stub/StubNetworkStorageTest.java b/src/test/java/networking/stub/StubNetworkStorageTest.java index bb73a1bb..37336c97 100644 --- a/src/test/java/networking/stub/StubNetworkStorageTest.java +++ b/src/test/java/networking/stub/StubNetworkStorageTest.java @@ -1,9 +1,25 @@ package networking.stub; +import java.util.ArrayList; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import model.Entity; +import model.exceptions.LightChainDistributedStorageException; +import network.Conduit; +import networking.MockEngine; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import unittest.fixtures.EntityFixture; + /** * Encapsulates tests for the storage side of the stub network. */ public class StubNetworkStorageTest { + private final String channel1 = "test-network-channel-1"; + private final String channel2 = "test-network-channel-2"; + private Hub hub; // TODO: implement test scenarios // Use mock engines with stub network. // 1. Engine A1 (on one network) puts an entity on channel1 and Engine B1 on another network can get it on the @@ -14,4 +30,138 @@ public class StubNetworkStorageTest { // 3. Engine A1 (on one network) can CONCURRENTLY put 100 different entities on channel1, and // Engine B1 on another network can get all of them at once using allEntities method, // while Engine B2 on another channel2 can't get none of them using all. + + /** + * Put an entity on channel1 from A1 and Engine B1 on another network can get it. + */ + @Test + void testPutOneEntity() { + this.hub = new Hub(); + StubNetwork stubNetwork1 = new StubNetwork(hub); + MockEngine a1 = new MockEngine(); + Conduit cA1 = stubNetwork1.register(a1, channel1); + /* + Create the other network. + */ + StubNetwork stubNetwork2 = new StubNetwork(hub); + MockEngine b1 = new MockEngine(); + Conduit cB1 = stubNetwork2.register(b1, channel1); + MockEngine b2 = new MockEngine(); + Conduit cB2 = stubNetwork2.register(b2, channel2); + Entity entity = new EntityFixture(); + try { + cA1.put(entity); + if (!cB1.get(entity.id()).equals(entity) || cB2.get(entity.id()) != null) { + Assertions.fail(); + } + } catch (LightChainDistributedStorageException e) { + Assertions.fail(); + } + } + + /** + * Put 100 entities on channel1 concurrently and test whether correct engine on the other channel can get it. + */ + @Test + void putEntityConcurrently() { + this.hub = new Hub(); + + int concurrencyDegree = 100; + AtomicInteger threadError = new AtomicInteger(); + CountDownLatch putDone = new CountDownLatch(concurrencyDegree); + Thread[] entityThreads = new Thread[concurrencyDegree]; + + StubNetwork stubNetwork1 = new StubNetwork(hub); + MockEngine a1 = new MockEngine(); + Conduit cA1 = stubNetwork1.register(a1, channel1); + + StubNetwork stubNetwork2 = new StubNetwork(hub); + MockEngine b1 = new MockEngine(); + Conduit cB1 = stubNetwork2.register(b1, channel1); + MockEngine b2 = new MockEngine(); + Conduit cB2 = stubNetwork2.register(b2, channel2); + for (int i = 0; i < concurrencyDegree; i++) { + entityThreads[i] = new Thread(() -> { + Entity entity = new EntityFixture(); + try { + cA1.put(entity); + putDone.countDown(); + if (!cB1.get(entity.id()).equals(entity) || cB2.get(entity.id()) != null) { + threadError.getAndIncrement(); + } + } catch (LightChainDistributedStorageException e) { + threadError.getAndIncrement(); + } + }); + } + for (Thread t : entityThreads) { + t.start(); + } + try { + boolean doneOneTime = putDone.await(60, TimeUnit.SECONDS); + Assertions.assertTrue(doneOneTime); + } catch (InterruptedException e) { + Assertions.fail(); + } + Assertions.assertEquals(0, threadError.get()); + } + + /** + * Put 100 entities on channel1 concurrently and test whether correct engine on the other channel can get with All. + */ + @Test + void testAllMethodConcurrently() { + this.hub = new Hub(); + + int concurrencyDegree = 100; + AtomicInteger threadError = new AtomicInteger(); + CountDownLatch putDone = new CountDownLatch(concurrencyDegree); + Thread[] entityThreads = new Thread[concurrencyDegree]; + + StubNetwork stubNetwork1 = new StubNetwork(hub); + MockEngine a1 = new MockEngine(); + Conduit cA1 = stubNetwork1.register(a1, channel1); + + StubNetwork stubNetwork2 = new StubNetwork(hub); + MockEngine b1 = new MockEngine(); + Conduit cB1 = stubNetwork2.register(b1, channel1); + MockEngine b2 = new MockEngine(); + Conduit cB2 = stubNetwork2.register(b2, channel2); + ArrayList allEntities = new ArrayList<>(); + for (int i = 0; i < concurrencyDegree; i++) { + allEntities.add(new EntityFixture()); + } + for (int i = 0; i < concurrencyDegree; i++) { + int finalI = i; + entityThreads[i] = new Thread(() -> { + try { + cA1.put(allEntities.get(finalI)); + putDone.countDown(); + } catch (LightChainDistributedStorageException e) { + threadError.getAndIncrement(); + } + }); + } + for (Thread t : entityThreads) { + t.start(); + } + // Check the allEntities method. + try { + boolean doneOneTime = putDone.await(60, TimeUnit.SECONDS); + Assertions.assertTrue(doneOneTime); + } catch (InterruptedException e) { + Assertions.fail(); + } + try { + ArrayList firstChannelEntities = cB1.allEntities(); + ArrayList secondChannelEntities = cB2.allEntities(); + for (Entity entity : allEntities) { + Assertions.assertTrue(firstChannelEntities.contains(entity) + && !secondChannelEntities.contains(entity)); + } + } catch (LightChainDistributedStorageException e) { + Assertions.fail(); + } + Assertions.assertEquals(0, threadError.get()); + } } diff --git a/src/test/java/unittest/fixtures/MerkleTreeFixture.java b/src/test/java/unittest/fixtures/MerkleTreeFixture.java new file mode 100644 index 00000000..ebf0347c --- /dev/null +++ b/src/test/java/unittest/fixtures/MerkleTreeFixture.java @@ -0,0 +1,22 @@ +package unittest.fixtures; + +import modules.ads.merkletree.MerkleTree; + +/** + * Creates a new randomly looking MerkleTree. + */ +public class MerkleTreeFixture { + /** + * Creates a new skip list with n random elements. + * + * @param n number of elements to create + * @return a new merkle tree with n random elements. + */ + public static MerkleTree createMerkleTree(int n) { + MerkleTree merkleTree = new MerkleTree(); + for (int i = 0; i < n; i++) { + merkleTree.put(new EntityFixture()); + } + return merkleTree; + } +} diff --git a/src/test/java/unittest/fixtures/Sha3256HashFixture.java b/src/test/java/unittest/fixtures/Sha3256HashFixture.java index 2a0bc7e6..41c5bbc7 100644 --- a/src/test/java/unittest/fixtures/Sha3256HashFixture.java +++ b/src/test/java/unittest/fixtures/Sha3256HashFixture.java @@ -1,5 +1,7 @@ package unittest.fixtures; +import java.util.ArrayList; + import model.crypto.Sha3256Hash; /** @@ -28,4 +30,18 @@ public static model.crypto.Sha3256Hash[] newSha3256HashArray() { } return hashArray; } + + /** + * Generates an ArrayList of random looking SHA3-256 hash values. + * + * @param size size of array list. + * @return an array filled with randomly generated SHA3-256 hash values. + */ + public static ArrayList newSha3256HashArrayList(int size) { + ArrayList hashArray = new ArrayList<>(); + for (int i = 0; i < size; i++) { + hashArray.add(newSha3256Hash()); + } + return hashArray; + } } From b1c71e59b8356dde38a668dfc38a294788dccd65 Mon Sep 17 00:00:00 2001 From: akucukoduk16 Date: Mon, 25 Apr 2022 16:45:43 +0300 Subject: [PATCH 2/5] Some style modifications --- src/main/java/network/NetworkAdapter.java | 4 ++-- src/test/java/networking/stub/StubNetworkStorageTest.java | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/network/NetworkAdapter.java b/src/main/java/network/NetworkAdapter.java index 14e46911..956f435c 100644 --- a/src/main/java/network/NetworkAdapter.java +++ b/src/main/java/network/NetworkAdapter.java @@ -1,12 +1,12 @@ package network; +import java.util.ArrayList; + import model.Entity; import model.exceptions.LightChainDistributedStorageException; import model.exceptions.LightChainNetworkingException; import model.lightchain.Identifier; -import java.util.ArrayList; - /** * NetworkAdapter models the interface that is exposed to the conduits from the networking layer. */ diff --git a/src/test/java/networking/stub/StubNetworkStorageTest.java b/src/test/java/networking/stub/StubNetworkStorageTest.java index e041bcee..7f7e47d8 100644 --- a/src/test/java/networking/stub/StubNetworkStorageTest.java +++ b/src/test/java/networking/stub/StubNetworkStorageTest.java @@ -40,9 +40,9 @@ void testPutOneEntity() { StubNetwork stubNetwork1 = new StubNetwork(hub); MockEngine a1 = new MockEngine(); Conduit ca1 = stubNetwork1.register(a1, channel1); - /* - Create the other network. - */ + /* + Create the other network. + */ StubNetwork stubNetwork2 = new StubNetwork(hub); MockEngine b1 = new MockEngine(); Conduit cb1 = stubNetwork2.register(b1, channel1); From 97a554f25c928905d1b5d2da5777d87c01c2bc1f Mon Sep 17 00:00:00 2001 From: akucukoduk16 Date: Mon, 25 Apr 2022 16:46:08 +0300 Subject: [PATCH 3/5] Some style modifications --- src/test/java/networking/stub/StubNetwork.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/test/java/networking/stub/StubNetwork.java b/src/test/java/networking/stub/StubNetwork.java index 487eabb3..13e14352 100644 --- a/src/test/java/networking/stub/StubNetwork.java +++ b/src/test/java/networking/stub/StubNetwork.java @@ -18,8 +18,6 @@ * A mock implementation of networking layer as a test util. */ public class StubNetwork implements Network, NetworkAdapter { - private final String channel1 = "test-network-channel-1"; - private final String channel2 = "test-network-channel-2"; private final ConcurrentHashMap engines; private final Hub hub; private final Identifier identifier; From bc588bd31178df1a5de594aca1b9d41e287d72fd Mon Sep 17 00:00:00 2001 From: akucukoduk16 Date: Sun, 22 May 2022 14:23:30 +0300 Subject: [PATCH 4/5] tests are fixed --- .../stub/StubNetworkStorageTest.java | 172 ++++++++++-------- 1 file changed, 100 insertions(+), 72 deletions(-) diff --git a/src/test/java/networking/stub/StubNetworkStorageTest.java b/src/test/java/networking/stub/StubNetworkStorageTest.java index 7f7e47d8..f3dc79ba 100644 --- a/src/test/java/networking/stub/StubNetworkStorageTest.java +++ b/src/test/java/networking/stub/StubNetworkStorageTest.java @@ -10,6 +10,7 @@ import network.Conduit; import networking.MockEngine; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import unittest.fixtures.EntityFixture; @@ -20,34 +21,38 @@ public class StubNetworkStorageTest { private final String channel1 = "test-network-channel-1"; private final String channel2 = "test-network-channel-2"; private Hub hub; - // TODO: implement test scenarios - // Use mock engines with stub network. - // 1. Engine A1 (on one network) puts an entity on channel1 and Engine B1 on another network can get it on the - // same channel1 successfully, while Engine B2 on another channel2 can't get it successfully. - // 2. Engine A1 (on one network) can CONCURRENTLY put 100 different entities on channel1, and - // Engine B1 on another network can get each entity using its entity id only the the same channel, - // while Engine B2 on another channel2 can't get it any of them successfully. - // 3. Engine A1 (on one network) can CONCURRENTLY put 100 different entities on channel1, and - // Engine B1 on another network can get all of them at once using allEntities method, - // while Engine B2 on another channel2 can't get none of them using all. + private StubNetwork stubNetwork1; + private StubNetwork stubNetwork2; + private MockEngine a1; + private MockEngine b1; + private MockEngine b2; + private Conduit ca1; + private Conduit cb1; + private Conduit cb2; + private ArrayList allEntities; + private ArrayList firstChannelEntities; + private ArrayList secondChannelEntities; + + @BeforeEach + void setUp() { + this.hub = new Hub(); + stubNetwork1 = new StubNetwork(hub); + a1 = new MockEngine(); + ca1 = stubNetwork1.register(a1, channel1); + stubNetwork2 = new StubNetwork(hub); + b1 = new MockEngine(); + cb1 = stubNetwork2.register(b1, channel1); + b2 = new MockEngine(); + cb2 = stubNetwork2.register(b2, channel2); + } /** - * Put an entity on channel1 from A1 and Engine B1 on another network can get it. + * Engine A1 (on one network) puts an entity on channel1 and Engine B1 on another network can get it on the + * same channel1 successfully, while Engine B2 on another channel2 can't get it successfully. */ @Test void testPutOneEntity() { - this.hub = new Hub(); - StubNetwork stubNetwork1 = new StubNetwork(hub); - MockEngine a1 = new MockEngine(); - Conduit ca1 = stubNetwork1.register(a1, channel1); - /* - Create the other network. - */ - StubNetwork stubNetwork2 = new StubNetwork(hub); - MockEngine b1 = new MockEngine(); - Conduit cb1 = stubNetwork2.register(b1, channel1); - MockEngine b2 = new MockEngine(); - Conduit cb2 = stubNetwork2.register(b2, channel2); + Entity entity = new EntityFixture(); try { ca1.put(entity); @@ -60,35 +65,47 @@ void testPutOneEntity() { } /** - * Put 100 entities on channel1 concurrently and test whether correct engine on the other channel can get it. + * Engine A1 (on one network) can CONCURRENTLY put 100 different entities on channel1, and + * Engine B1 on another network can get each entity using its entity id only the the same channel, + * while Engine B2 on another channel2 can't get it any of them successfully. */ @Test void putEntityConcurrently() { - this.hub = new Hub(); + this.putEntityConcurrentlyFunction(); + this.getEntityConcurrentlyFunction(); + } - int concurrencyDegree = 100; - AtomicInteger threadError = new AtomicInteger(); - CountDownLatch putDone = new CountDownLatch(concurrencyDegree); - Thread[] entityThreads = new Thread[concurrencyDegree]; + /** + * Engine A1 (on one network) can CONCURRENTLY put 100 different entities on channel1, and + * Engine B1 on another network can get all of them at once using allEntities method, + * while Engine B2 on another channel2 can't get none of them using all. + */ + @Test + void testAllMethodConcurrently() { + this.putEntityConcurrentlyFunction(); + this.checkAllConcurrently(); + } - StubNetwork stubNetwork1 = new StubNetwork(hub); - MockEngine a1 = new MockEngine(); - Conduit ca1 = stubNetwork1.register(a1, channel1); + /** + * Engine B1 on another network can get all of them at once using allEntities method, + * while Engine B2 on another channel2 can't get none of them using all. + */ + private void checkAllConcurrently() { + int concurrencyDegree = 2; + AtomicInteger threadError = new AtomicInteger(); - StubNetwork stubNetwork2 = new StubNetwork(hub); - MockEngine b1 = new MockEngine(); - Conduit cb1 = stubNetwork2.register(b1, channel1); - MockEngine b2 = new MockEngine(); - Conduit cb2 = stubNetwork2.register(b2, channel2); + CountDownLatch allDone = new CountDownLatch(concurrencyDegree); + Thread[] entityThreads = new Thread[concurrencyDegree]; for (int i = 0; i < concurrencyDegree; i++) { + int finalI = i; entityThreads[i] = new Thread(() -> { - Entity entity = new EntityFixture(); try { - ca1.put(entity); - putDone.countDown(); - if (!cb1.get(entity.id()).equals(entity) || cb2.get(entity.id()) != null) { - threadError.getAndIncrement(); + if (finalI == 0) { + firstChannelEntities = cb1.allEntities(); + } else { + secondChannelEntities = cb2.allEntities(); } + allDone.countDown(); } catch (LightChainDistributedStorageException e) { threadError.getAndIncrement(); } @@ -98,44 +115,31 @@ void putEntityConcurrently() { t.start(); } try { - boolean doneOneTime = putDone.await(60, TimeUnit.SECONDS); + boolean doneOneTime = allDone.await(60, TimeUnit.SECONDS); Assertions.assertTrue(doneOneTime); } catch (InterruptedException e) { Assertions.fail(); } Assertions.assertEquals(0, threadError.get()); + Assertions.assertTrue(firstChannelEntities.containsAll(allEntities)); + Assertions.assertTrue(secondChannelEntities.isEmpty()); } /** - * Put 100 entities on channel1 concurrently and test whether correct engine on the other channel can get with All. + * Engine A1 (on one network) can CONCURRENTLY put 100 different entities on channel1 */ - @Test - void testAllMethodConcurrently() { - this.hub = new Hub(); - + private void putEntityConcurrentlyFunction() { int concurrencyDegree = 100; AtomicInteger threadError = new AtomicInteger(); CountDownLatch putDone = new CountDownLatch(concurrencyDegree); Thread[] entityThreads = new Thread[concurrencyDegree]; - - StubNetwork stubNetwork1 = new StubNetwork(hub); - MockEngine a1 = new MockEngine(); - Conduit ca1 = stubNetwork1.register(a1, channel1); - - StubNetwork stubNetwork2 = new StubNetwork(hub); - MockEngine b1 = new MockEngine(); - Conduit cb1 = stubNetwork2.register(b1, channel1); - MockEngine b2 = new MockEngine(); - Conduit cb2 = stubNetwork2.register(b2, channel2); - ArrayList allEntities = new ArrayList<>(); + allEntities = new ArrayList<>(); for (int i = 0; i < concurrencyDegree; i++) { - allEntities.add(new EntityFixture()); - } - for (int i = 0; i < concurrencyDegree; i++) { - int finalI = i; entityThreads[i] = new Thread(() -> { + Entity entity = new EntityFixture(); + allEntities.add(entity); try { - ca1.put(allEntities.get(finalI)); + ca1.put(entity); putDone.countDown(); } catch (LightChainDistributedStorageException e) { threadError.getAndIncrement(); @@ -145,21 +149,45 @@ void testAllMethodConcurrently() { for (Thread t : entityThreads) { t.start(); } - // Check the allEntities method. try { boolean doneOneTime = putDone.await(60, TimeUnit.SECONDS); Assertions.assertTrue(doneOneTime); } catch (InterruptedException e) { Assertions.fail(); } + Assertions.assertEquals(0, threadError.get()); + } + + /** + * Engine B1 on another network can get it on the + * same channel1 successfully, while Engine B2 on another channel2 can't get it successfully. + */ + private void getEntityConcurrentlyFunction() { + int concurrencyDegree = 100; + AtomicInteger threadError = new AtomicInteger(); + CountDownLatch getDone = new CountDownLatch(concurrencyDegree); + Thread[] entityThreads = new Thread[concurrencyDegree]; + for (int i = 0; i < concurrencyDegree; i++) { + int finalI = i; + entityThreads[i] = new Thread(() -> { + Entity entity = allEntities.get(finalI); + try { + if (!cb1.get(entity.id()).equals(entity) || cb2.get(entity.id()) != null) { + threadError.getAndIncrement(); + } + getDone.countDown(); + } catch (LightChainDistributedStorageException e) { + threadError.getAndIncrement(); + } + }); + } + for (Thread t : entityThreads) { + t.start(); + } try { - ArrayList firstChannelEntities = cb1.allEntities(); - ArrayList secondChannelEntities = cb2.allEntities(); - for (Entity entity : allEntities) { - Assertions.assertTrue(firstChannelEntities.contains(entity) - && !secondChannelEntities.contains(entity)); - } - } catch (LightChainDistributedStorageException e) { + boolean doneOneTime = getDone.await(60, TimeUnit.SECONDS); + Assertions.assertTrue(doneOneTime); + } catch (InterruptedException e) { Assertions.fail(); } Assertions.assertEquals(0, threadError.get()); From bd093d9819cc486f18a6a5b0bb18aaa66da1738f Mon Sep 17 00:00:00 2001 From: akucukoduk16 Date: Sun, 22 May 2022 14:26:10 +0300 Subject: [PATCH 5/5] sytle fixed --- src/test/java/networking/stub/StubNetworkStorageTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/networking/stub/StubNetworkStorageTest.java b/src/test/java/networking/stub/StubNetworkStorageTest.java index f3dc79ba..1062b307 100644 --- a/src/test/java/networking/stub/StubNetworkStorageTest.java +++ b/src/test/java/networking/stub/StubNetworkStorageTest.java @@ -126,7 +126,7 @@ private void checkAllConcurrently() { } /** - * Engine A1 (on one network) can CONCURRENTLY put 100 different entities on channel1 + * Engine A1 (on one network) can CONCURRENTLY put 100 different entities on channel1. */ private void putEntityConcurrentlyFunction() { int concurrencyDegree = 100;