From e5eebb71595879d10b22d6057b9db046036a09ad Mon Sep 17 00:00:00 2001 From: Ozan Nacitarhan Date: Sun, 24 Apr 2022 14:20:06 +0300 Subject: [PATCH 01/12] implements on-disk Merkle Tree and one basic test of it + serializaion improvements --- src/main/java/model/crypto/Hash.java | 4 +- src/main/java/model/crypto/Sha3256Hash.java | 3 +- .../java/model/lightchain/Identifier.java | 3 +- .../modules/ads/merkletree/MerkleNode.java | 12 ++- .../modules/ads/merkletree/MerkleTree.java | 27 +++++- src/main/java/storage/MerkleNodes.java | 12 +++ src/main/java/storage/MerkleTrees.java | 12 +++ .../java/storage/mapdb/MerkleNodeMapDb.java | 70 +++++++++++++++ .../java/storage/mapdb/MerkleTreeMapDb.java | 87 +++++++++++++++++++ src/test/java/storage/MerkleTreesTest.java | 65 ++++++++++++++ .../java/unittest/fixtures/EntityFixture.java | 3 +- .../unittest/fixtures/MerkleTreeFixture.java | 20 ++++- 12 files changed, 311 insertions(+), 7 deletions(-) create mode 100644 src/main/java/storage/MerkleNodes.java create mode 100644 src/main/java/storage/MerkleTrees.java create mode 100644 src/main/java/storage/mapdb/MerkleNodeMapDb.java create mode 100644 src/main/java/storage/mapdb/MerkleTreeMapDb.java create mode 100644 src/test/java/storage/MerkleTreesTest.java diff --git a/src/main/java/model/crypto/Hash.java b/src/main/java/model/crypto/Hash.java index 2a02985a..d5c92531 100644 --- a/src/main/java/model/crypto/Hash.java +++ b/src/main/java/model/crypto/Hash.java @@ -1,11 +1,13 @@ package model.crypto; +import java.io.Serializable; + import model.lightchain.Identifier; /** * Represents abstract data type for the cryptographic hash function used in LightChain. */ -public abstract class Hash { +public abstract class Hash implements Serializable { /** * Actual value of hash in bytes. */ diff --git a/src/main/java/model/crypto/Sha3256Hash.java b/src/main/java/model/crypto/Sha3256Hash.java index 7943a858..ffe50277 100644 --- a/src/main/java/model/crypto/Sha3256Hash.java +++ b/src/main/java/model/crypto/Sha3256Hash.java @@ -1,5 +1,6 @@ package model.crypto; +import java.io.Serializable; import java.util.Arrays; import model.lightchain.Identifier; @@ -8,7 +9,7 @@ * Represents SHA3-256 data type which extends abstract Hash data type for * the cryptographic hash function used in LightChain. */ -public class Sha3256Hash extends Hash { +public class Sha3256Hash extends Hash implements Serializable { public static final int Size = 32; private final byte[] hashBytes; diff --git a/src/main/java/model/lightchain/Identifier.java b/src/main/java/model/lightchain/Identifier.java index 26c546ff..7306ffbf 100644 --- a/src/main/java/model/lightchain/Identifier.java +++ b/src/main/java/model/lightchain/Identifier.java @@ -1,5 +1,6 @@ package model.lightchain; +import java.io.Serializable; import java.util.Arrays; import io.ipfs.multibase.Multibase; @@ -7,7 +8,7 @@ /** * Represents a 32-byte unique identifier for an entity. Normally is computed as the hash value of the entity. */ -public class Identifier { +public class Identifier implements Serializable { public static final int Size = 32; private final byte[] value; diff --git a/src/main/java/modules/ads/merkletree/MerkleNode.java b/src/main/java/modules/ads/merkletree/MerkleNode.java index 4551b1bf..10548590 100644 --- a/src/main/java/modules/ads/merkletree/MerkleNode.java +++ b/src/main/java/modules/ads/merkletree/MerkleNode.java @@ -1,5 +1,9 @@ package modules.ads.merkletree; +import java.io.Serializable; +import java.nio.charset.StandardCharsets; + +import com.google.gson.Gson; import crypto.Sha3256Hasher; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import model.Entity; @@ -8,7 +12,7 @@ /** * A node in the Merkle tree. */ -public class MerkleNode { +public class MerkleNode implements Serializable { private static final Sha3256Hasher hasher = new Sha3256Hasher(); private MerkleNode left; private MerkleNode right; @@ -114,4 +118,10 @@ public MerkleNode getSibling() { return parent.getLeft(); } } + + public byte[] getBytes() { + Gson gson = new Gson(); + byte[] bytes = gson.toJson(this).getBytes(StandardCharsets.UTF_8); + return bytes; + } } diff --git a/src/main/java/modules/ads/merkletree/MerkleTree.java b/src/main/java/modules/ads/merkletree/MerkleTree.java index 2c194c4a..b3cfc061 100644 --- a/src/main/java/modules/ads/merkletree/MerkleTree.java +++ b/src/main/java/modules/ads/merkletree/MerkleTree.java @@ -1,10 +1,16 @@ package modules.ads.merkletree; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.concurrent.locks.ReentrantReadWriteLock; +import com.google.gson.Gson; import crypto.Sha3256Hasher; import model.Entity; import model.crypto.Sha3256Hash; @@ -15,7 +21,7 @@ * Implementation of an in-memory Authenticated Skip List * that is capable of storing and retrieval of LightChain entities. */ -public class MerkleTree implements AuthenticatedDataStructure { +public class MerkleTree implements AuthenticatedDataStructure, Serializable { private static final Sha3256Hasher hasher = new Sha3256Hasher(); private final ReentrantReadWriteLock lock; private final ArrayList leafNodes; @@ -133,4 +139,23 @@ private void buildMerkleTree() { public int size() { return this.size; } + + public byte[] getBytes() { + try { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ObjectOutputStream out = null; + out = new ObjectOutputStream(bos); + out.writeObject(this); + out.flush(); + byte[] bytes = bos.toByteArray(); + return bytes; + } catch (IOException e) { + e.printStackTrace(); + } + /* + Gson gson = new Gson(); + byte[] bytes = gson.toJson(this).getBytes(StandardCharsets.UTF_8); + */ + return null; + } } diff --git a/src/main/java/storage/MerkleNodes.java b/src/main/java/storage/MerkleNodes.java new file mode 100644 index 00000000..24dc668c --- /dev/null +++ b/src/main/java/storage/MerkleNodes.java @@ -0,0 +1,12 @@ +package storage; + +import java.util.ArrayList; + +import modules.ads.merkletree.MerkleNode; + +public interface MerkleNodes { + boolean add(MerkleNode node); + boolean has(MerkleNode node); + boolean remove(MerkleNode node); + ArrayList all(); +} diff --git a/src/main/java/storage/MerkleTrees.java b/src/main/java/storage/MerkleTrees.java new file mode 100644 index 00000000..1c479f48 --- /dev/null +++ b/src/main/java/storage/MerkleTrees.java @@ -0,0 +1,12 @@ +package storage; + +import java.util.ArrayList; + +import modules.ads.merkletree.MerkleTree; + +public interface MerkleTrees { + boolean add(MerkleTree tree); + boolean has(MerkleTree tree); + boolean remove(MerkleTree tree); + ArrayList all(); +} diff --git a/src/main/java/storage/mapdb/MerkleNodeMapDb.java b/src/main/java/storage/mapdb/MerkleNodeMapDb.java new file mode 100644 index 00000000..2c7f748e --- /dev/null +++ b/src/main/java/storage/mapdb/MerkleNodeMapDb.java @@ -0,0 +1,70 @@ +package storage.mapdb; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import com.google.gson.Gson; +import modules.ads.merkletree.MerkleNode; +import org.mapdb.DB; +import org.mapdb.DBMaker; +import org.mapdb.HTreeMap; +import org.mapdb.Serializer; +import storage.MerkleNodes; + +public class MerkleNodeMapDb implements MerkleNodes { + private final DB db; + private final ReentrantReadWriteLock lock; + private static final String MAP_NAME = "merkle_node_map"; + private final HTreeMap merkleNodeMap; + + public MerkleNodeMapDb(String filePath) { + this.db = DBMaker.fileDB(filePath).make(); + this.lock = new ReentrantReadWriteLock(); + this.merkleNodeMap = db.hashMap(MAP_NAME). + keySerializer(Serializer.BYTE_ARRAY) + .valueSerializer(Serializer.BYTE_ARRAY) + .createOrOpen(); + } + + @Override + public boolean add(MerkleNode node) { + boolean addBoolean; + try { + lock.writeLock().lock(); + addBoolean = merkleNodeMap.putIfAbsentBoolean(node.getBytes(), node.getBytes()); + } finally { + lock.writeLock().unlock(); + } + return addBoolean; + } + + @Override + public boolean has(MerkleNode node) { + boolean hasBoolean; + try { + lock.readLock().lock(); + hasBoolean = merkleNodeMap.containsKey(node.getBytes()); + } finally { + lock.readLock().unlock(); + } + return hasBoolean; + } + + @Override + public boolean remove(MerkleNode node) { + return merkleNodeMap.remove(node.getBytes(), node.getBytes()); + } + + @Override + public ArrayList all() { + ArrayList nodes = new ArrayList<>(); + for (byte[] element : merkleNodeMap.keySet()) { + Gson gson = new Gson(); + String json = new String(element.clone(), StandardCharsets.UTF_8); + MerkleNode node = gson.fromJson(json, MerkleNode.class); + nodes.add(node); + } + return nodes; + } +} diff --git a/src/main/java/storage/mapdb/MerkleTreeMapDb.java b/src/main/java/storage/mapdb/MerkleTreeMapDb.java new file mode 100644 index 00000000..8d3189da --- /dev/null +++ b/src/main/java/storage/mapdb/MerkleTreeMapDb.java @@ -0,0 +1,87 @@ +package storage.mapdb; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import com.google.gson.Gson; +import modules.ads.merkletree.MerkleTree; +import org.mapdb.DB; +import org.mapdb.DBMaker; +import org.mapdb.HTreeMap; +import org.mapdb.Serializer; +import storage.MerkleTrees; + +public class MerkleTreeMapDb implements MerkleTrees { + private final DB db; + private final ReentrantReadWriteLock lock; + private static final String MAP_NAME = "merkle_tree_map"; + private final HTreeMap merkleTreeMap; + + public MerkleTreeMapDb(String filePath) { + this.db = DBMaker.fileDB(filePath).make(); + this.lock = new ReentrantReadWriteLock(); + this.merkleTreeMap = db.hashMap(MAP_NAME). + keySerializer(Serializer.BYTE_ARRAY) + .valueSerializer(Serializer.BYTE_ARRAY) + .createOrOpen(); + } + + @Override + public boolean add(MerkleTree tree) { + boolean addBoolean; + try { + lock.writeLock().lock(); + addBoolean = merkleTreeMap.putIfAbsentBoolean(tree.getBytes(), tree.getBytes()); + } finally { + lock.writeLock().unlock(); + } + return addBoolean; + } + + @Override + public boolean has(MerkleTree tree) { + boolean hasBoolean; + try { + lock.readLock().lock(); + hasBoolean = merkleTreeMap.containsKey(tree.getBytes()); + } finally { + lock.readLock().unlock(); + } + return hasBoolean; + } + + @Override + public boolean remove(MerkleTree tree) { + return merkleTreeMap.remove(tree.getBytes(), tree.getBytes()); + } + + @Override + public ArrayList all() { + ArrayList trees = new ArrayList<>(); + for (byte[] element : merkleTreeMap.keySet()) { + try { + ByteArrayInputStream bis = new ByteArrayInputStream(element); + ObjectInputStream inp = null; + inp = new ObjectInputStream(bis); + MerkleTree tree = (MerkleTree) inp.readObject(); + /* + Gson gson = new Gson(); + String json = new String(element.clone(), StandardCharsets.UTF_8); + MerkleTree tree = gson.fromJson(json, MerkleTree.class); + */ + trees.add(tree); + } catch (IOException e) { + e.printStackTrace(); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + return trees; + } + + public void closeDb() { + db.close(); + } +} diff --git a/src/test/java/storage/MerkleTreesTest.java b/src/test/java/storage/MerkleTreesTest.java new file mode 100644 index 00000000..3f5d9813 --- /dev/null +++ b/src/test/java/storage/MerkleTreesTest.java @@ -0,0 +1,65 @@ +package storage; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; + +import model.Entity; +import modules.ads.AuthenticatedEntity; +import modules.ads.merkletree.MerkleTree; +import modules.ads.merkletree.MerkleTreeAuthenticatedEntityVerifier; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.testcontainers.shaded.org.apache.commons.io.FileUtils; +import storage.mapdb.MerkleTreeMapDb; +import unittest.fixtures.EntityFixture; +import unittest.fixtures.MerkleTreeFixture; + +public class MerkleTreesTest { + private static final String TEMP_DIR = "tempdir"; + private static final String TEMP_FILE = "tempfile.db"; + private Path tempdir; + private ArrayList allTrees; + private MerkleTreeMapDb db; + + /** + * Sets up the tests. + * + * @throws IOException if the temp directory cannot be created + */ + @BeforeEach + void setUp() throws IOException { + Path currentRelativePath = Paths.get(""); + tempdir = Files.createTempDirectory(currentRelativePath, TEMP_DIR); + db = new MerkleTreeMapDb(tempdir.toAbsolutePath() + "/" + TEMP_FILE); + allTrees = MerkleTreeFixture.newMerkleTrees(10, 5); + } + + @Test + public void testVerification() throws IOException { + for (MerkleTree tree : allTrees) { + Assertions.assertTrue(db.add(tree)); + } + for (MerkleTree tree : allTrees) { + Assertions.assertTrue(db.has(tree)); + } + ArrayList all = db.all(); + Assertions.assertEquals(all.size(), 10); + for (MerkleTree merkleTree : all) { + 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)); + } + db.closeDb(); + FileUtils.deleteDirectory(new File(tempdir.toString())); + } + +} diff --git a/src/test/java/unittest/fixtures/EntityFixture.java b/src/test/java/unittest/fixtures/EntityFixture.java index 792a7658..554f7dbe 100644 --- a/src/test/java/unittest/fixtures/EntityFixture.java +++ b/src/test/java/unittest/fixtures/EntityFixture.java @@ -1,5 +1,6 @@ package unittest.fixtures; +import java.io.Serializable; import java.util.Arrays; import java.util.Objects; import java.util.Random; @@ -11,7 +12,7 @@ /** * Encapsulates test utilities for a LightChain entity. */ -public class EntityFixture extends Entity { +public class EntityFixture extends Entity implements Serializable { private static final String TYPE_FIXTURE_ENTITY = "fixture-entity-type"; private static final Random rand = new Random(); private final Identifier id; diff --git a/src/test/java/unittest/fixtures/MerkleTreeFixture.java b/src/test/java/unittest/fixtures/MerkleTreeFixture.java index ebf0347c..dfa603bd 100644 --- a/src/test/java/unittest/fixtures/MerkleTreeFixture.java +++ b/src/test/java/unittest/fixtures/MerkleTreeFixture.java @@ -1,5 +1,8 @@ package unittest.fixtures; +import java.util.ArrayList; + +import model.lightchain.Identifier; import modules.ads.merkletree.MerkleTree; /** @@ -7,7 +10,7 @@ */ public class MerkleTreeFixture { /** - * Creates a new skip list with n random elements. + * Creates a new merkle tree with n random elements. * * @param n number of elements to create * @return a new merkle tree with n random elements. @@ -19,4 +22,19 @@ public static MerkleTree createMerkleTree(int n) { } return merkleTree; } + + /** + * Creates n new merkle trees with m random elements. + * + * @param n number of trees to create + * @param m number of elements in each tree + * @return an array list of n merkle trees with m random elements. + */ + public static ArrayList newMerkleTrees(int n, int m) { + ArrayList trees = new ArrayList<>(); + for (int i = 0; i < n; i++) { + trees.add(createMerkleTree(m)); + } + return trees; + } } From 61adb77f29623414285d1d8b660b39ca5a08c668 Mon Sep 17 00:00:00 2001 From: Ozan Nacitarhan Date: Sun, 24 Apr 2022 20:18:35 +0300 Subject: [PATCH 02/12] makes MerkleTree tests generic --- src/main/java/model/codec/EntityType.java | 1 + src/main/java/model/lightchain/Account.java | 26 +- src/test/java/modules/ads/MerkleTreeTest.java | 232 ++++++++++++------ 3 files changed, 188 insertions(+), 71 deletions(-) diff --git a/src/main/java/model/codec/EntityType.java b/src/main/java/model/codec/EntityType.java index 2be05de7..34d2d85a 100644 --- a/src/main/java/model/codec/EntityType.java +++ b/src/main/java/model/codec/EntityType.java @@ -9,4 +9,5 @@ public class EntityType { public static final String TYPE_VALIDATED_TRANSACTION = "type-lightchain-validated-transaction"; public static final String TYPE_VALIDATED_BLOCK = "type-lightchain-validated-block"; public static final String TYPE_ECDSA_SIGNATURE = "type-lightchain-ecdsa-signature"; + public static final String TYPE_ACCOUNT = "type-lightchain-account"; } diff --git a/src/main/java/model/lightchain/Account.java b/src/main/java/model/lightchain/Account.java index df046c0b..1bdabc2f 100644 --- a/src/main/java/model/lightchain/Account.java +++ b/src/main/java/model/lightchain/Account.java @@ -1,11 +1,13 @@ package model.lightchain; +import model.Entity; +import model.codec.EntityType; import model.crypto.PublicKey; /** * Represents a LightChain account which is the constituent of the SnapShot. */ -public class Account { +public class Account extends Entity { /** * Unique identifier of the account. */ @@ -72,4 +74,24 @@ public Identifier getLastBlockId() { public int getStake() { return this.stake; } -} + + /** + * Type of this entity. + * + * @return type of this entity. + */ + @Override + public String type() { + return EntityType.TYPE_ACCOUNT; + } + + /** + * Identifier of this account. + * + * @return identifier of this Account + */ + @Override + public Identifier id() { + return this.identifier; + } +} \ No newline at end of file diff --git a/src/test/java/modules/ads/MerkleTreeTest.java b/src/test/java/modules/ads/MerkleTreeTest.java index 95a290dc..217f724f 100644 --- a/src/test/java/modules/ads/MerkleTreeTest.java +++ b/src/test/java/modules/ads/MerkleTreeTest.java @@ -6,6 +6,7 @@ import java.util.concurrent.atomic.AtomicInteger; import model.Entity; +import model.codec.EntityType; import model.crypto.Sha3256Hash; import model.lightchain.Identifier; import modules.ads.merkletree.MerkleProof; @@ -14,9 +15,7 @@ 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; +import unittest.fixtures.*; /** * Encapsulates tests for an authenticated and concurrent implementation of MerkleTree ADS. @@ -24,14 +23,89 @@ public class MerkleTreeTest { /** - * A basic test for one sequential put and get operations. + * Test putting and verifying a random entity in a random merkle tree. */ @Test - public void testVerification() { - MerkleTree merkleTree = MerkleTreeFixture.createMerkleTree(5); - Assertions.assertEquals(merkleTree.size(), 5); // fixture sanity check. + public void testVerificationNoArg() { + testVerification(null, null); + } + + /** + * Tests both putting and getting the same random entity gives same proof and putting + * another entity gives different proofs. + */ + @Test + public void testPutGetSameProofNoArg() { + testPutGetSameProof(null, null); + } - Entity entity = new EntityFixture(); + /** + * Tests putting an existing entity does not change the proof with a random entity and merkle tree. + */ + @Test + public void testPutExistingEntityNoArg() { + testPutExistingEntity(null, null); + } + + /** + * Concurrently puts and gets entities and checks their proofs are correct (thread safety check). + */ + @Test + public void testConcurrentPutGetNoArg() { + testConcurrentPutGet(null, null); + } + + /** + * Tests getting an entity that does not exist in the merkle tree throws IllegalArgumentException + * with a random entity and merkle tree. + */ + @Test + public void testGetNonExistingEntityNoArg() { + testGetNonExistingEntity(null, null); + } + + /** + * Tests inserting null throws IllegalArgumentException with a random merkle tree. + */ + @Test + public void testNullInsertionNoArg() { + testNullInsertion(null); + } + + /** + * Tests the proof verification fails when root is changed with random entities and merkle tree. + */ + @Test + public void testManipulatedRootNoArg() { + testManipulatedRoot(null, null); + } + + /** + * Tests the proof verification fails when entity is changed with random entity and merkle tree. + */ + @Test + public void testManipulatedEntityNoArg() { + testManipulatedEntity(null, null); + } + + /** + * Tests the proof verification fails when entity is changed with random entity and merkle tree. + */ + @Test + public void testManipulatedProofNoArg() { + testManipulatedProof(null, null); + } + + /** + * Generic function to test putting and verifying an entity in a merkle tree. + * + * @param entity the entity to put in the merkle tree + * @param merkleTree the merkle tree to put the entity in + */ + public static void testVerification(Entity entity, MerkleTree merkleTree) { + entity = entity != null ? entity : new EntityFixture(); + merkleTree = merkleTree != null ? merkleTree : MerkleTreeFixture.createMerkleTree(5); + Assertions.assertEquals(merkleTree.size(), 5); // fixture sanity check. merkleTree.put(entity); Assertions.assertEquals(merkleTree.size(), 6); @@ -41,14 +115,16 @@ public void testVerification() { } /** - * Tests both putting and getting the same entity gives same proof + * Generic function to test both putting and getting the same entity gives same proof * and putting another entity gives different proofs. + * + * @param e1 the entity to put in the merkle tree + * @param merkleTree the merkle tree to put the entity in */ - @Test - public void testPutGetSameProof() { - MerkleTree merkleTree = MerkleTreeFixture.createMerkleTree(5); + public static void testPutGetSameProof(Entity e1, MerkleTree merkleTree) { + merkleTree = merkleTree != null ? merkleTree : MerkleTreeFixture.createMerkleTree(5); Assertions.assertEquals(merkleTree.size(), 5); // fixture sanity check. - Entity e1 = new EntityFixture(); + e1 = e1 != null ? e1 : new EntityFixture(); // putting e1 AuthenticatedEntity authenticatedEntityPut = merkleTree.put(e1); @@ -75,13 +151,15 @@ public void testPutGetSameProof() { } /** - * Tests putting an existing entity does not change the proof. + * Generic function which tests putting an existing entity does not change the proof. + * + * @param entity the entity to put in the merkle tree + * @param merkleTree the merkle tree to put the entity in */ - @Test - public void testPutExistingEntity() { - MerkleTree merkleTree = MerkleTreeFixture.createMerkleTree(5); + public static void testPutExistingEntity(Entity entity, MerkleTree merkleTree) { + entity = entity != null ? entity : new EntityFixture(); + merkleTree = merkleTree != null ? merkleTree : MerkleTreeFixture.createMerkleTree(5); Assertions.assertEquals(merkleTree.size(), 5); // fixture sanity check. - Entity entity = new EntityFixture(); // first time put AuthenticatedEntity authenticatedEntityPut = merkleTree.put(entity); @@ -98,10 +176,13 @@ public void testPutExistingEntity() { } /** - * Concurrently puts and gets entities and checks their proofs are correct (thread safety check). + * Generic function which concurrently puts and gets entities and checks their proofs are correct + * (thread safety check). + * + * @param type the type of entity which is put in the merkle tree + * @param merkleTree the merkle tree to put the entity in */ - @Test - public void testConcurrentPutGet() { + public static void testConcurrentPutGet(String type, MerkleTree merkleTree) { int concurrencyDegree = 100; ArrayList entities = new ArrayList<>(); ArrayList ids = new ArrayList<>(); @@ -113,11 +194,17 @@ public void testConcurrentPutGet() { Thread[] putThreads = new Thread[concurrencyDegree]; Thread[] getThreads = new Thread[concurrencyDegree]; - MerkleTree merkleTree = MerkleTreeFixture.createMerkleTree(0); + merkleTree = merkleTree != null ? merkleTree : MerkleTreeFixture.createMerkleTree(0); + MerkleTree finalMerkleTree = merkleTree; Assertions.assertEquals(merkleTree.size(), 0); // fixture sanity check. for (int i = 0; i < concurrencyDegree; i++) { - Entity entity = new EntityFixture(); + Entity entity; + if (type.equals(EntityType.TYPE_ACCOUNT)) { + entity = AccountFixture.newAccount(IdentifierFixture.newIdentifier()); + } else { + entity = new EntityFixture(); + } entities.add(entity); ids.add(entity.id()); } @@ -127,7 +214,7 @@ public void testConcurrentPutGet() { Entity entity = entities.get(i); putThreads[i] = new Thread(() -> { try { - merkleTree.put(entity); + finalMerkleTree.put(entity); putDone.countDown(); } catch (Exception e) { threadError.getAndIncrement(); @@ -149,7 +236,7 @@ public void testConcurrentPutGet() { Identifier id = ids.get(i); getThreads[i] = new Thread(() -> { try { - AuthenticatedEntity authenticatedEntity = merkleTree.get(id); + AuthenticatedEntity authenticatedEntity = finalMerkleTree.get(id); MerkleTreeAuthenticatedEntityVerifier verifier = new MerkleTreeAuthenticatedEntityVerifier(); if (!verifier.verify(authenticatedEntity)) { threadError.getAndIncrement(); @@ -174,48 +261,52 @@ public void testConcurrentPutGet() { } /** - * Tests getting an entity that does not exist in the merkle tree throws IllegalArgumentException. + * Generic function which tests getting an entity that does not exist in the merkle tree + * throws IllegalArgumentException. + * + * @param entity the entity to put in the merkle tree + * @param merkleTree the merkle tree to put the entity in */ - @Test - public void testGetNonExistingEntity() { - MerkleTree merkleTree = MerkleTreeFixture.createMerkleTree(5); + public static void testGetNonExistingEntity(Entity entity, MerkleTree merkleTree) { + merkleTree = merkleTree != null ? merkleTree : MerkleTreeFixture.createMerkleTree(5); Assertions.assertEquals(merkleTree.size(), 5); // fixture sanity check. - Entity entity = new EntityFixture(); - - Assertions.assertThrows(IllegalArgumentException.class, () -> { - merkleTree.get(entity.id()); - }); + entity = entity != null ? entity : new EntityFixture(); + Entity finalEntity = entity; + MerkleTree finalMerkleTree = merkleTree; + Assertions.assertThrows(IllegalArgumentException.class, () -> finalMerkleTree.get(finalEntity.id())); } /** - * Tests inserting null throws IllegalArgumentException. + * Generic function which tests inserting null throws IllegalArgumentException. + * + * @param merkleTree the merkle tree to put null */ - @Test - public void testNullInsertion() { - MerkleTree merkleTree = MerkleTreeFixture.createMerkleTree(5); + public static void testNullInsertion(MerkleTree merkleTree) { + merkleTree = merkleTree != null ? merkleTree : MerkleTreeFixture.createMerkleTree(5); Assertions.assertEquals(merkleTree.size(), 5); // fixture sanity check. - Assertions.assertThrows(IllegalArgumentException.class, () -> { - merkleTree.put(null); - }); + MerkleTree finalMerkleTree = merkleTree; + Assertions.assertThrows(IllegalArgumentException.class, () -> finalMerkleTree.put(null)); } /** - * Tests the proof verification fails when root is changed. + * Generic function which tests the proof verification fails when root is changed. + * + * @param entity the entity to put in the merkle tree + * @param merkleTree the merkle tree to put the entity in */ - @Test - public void testManipulatedRoot() { - MerkleTree merkleTree = MerkleTreeFixture.createMerkleTree(5); + public static void testManipulatedRoot(Entity entity, MerkleTree merkleTree) { + entity = entity != null ? entity : new EntityFixture(); + merkleTree = merkleTree != null ? 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()); + tamperedProof, + authenticatedEntity.type(), + authenticatedEntity.getEntity()); MerkleTreeAuthenticatedEntityVerifier verifier = new MerkleTreeAuthenticatedEntityVerifier(); // authenticated entity must be verified. @@ -225,19 +316,21 @@ public void testManipulatedRoot() { } /** - * Tests the proof verification fails when entity is changed. + * Generic function which tests the proof verification fails when entity is changed. + * + * @param entity the entity to put in the merkle tree + * @param merkleTree the merkle tree to put the entity in */ - @Test - public void testManipulatedEntity() { - MerkleTree merkleTree = MerkleTreeFixture.createMerkleTree(5); + public static void testManipulatedEntity(Entity entity, MerkleTree merkleTree) { + entity = entity != null ? entity : new EntityFixture(); + merkleTree = merkleTree != null ? 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()); + (MerkleProof) authenticatedEntity.getMembershipProof(), + authenticatedEntity.type(), + new EntityFixture()); MerkleTreeAuthenticatedEntityVerifier verifier = new MerkleTreeAuthenticatedEntityVerifier(); Assertions.assertTrue(verifier.verify(authenticatedEntity)); // original authenticated entity passes verification. @@ -245,24 +338,25 @@ public void testManipulatedEntity() { } /** - * Tests the proof fails verification when proof part of authenticated entity is changed. + * Generic function which tests the proof fails verification when proof part of authenticated entity is changed. + * + * @param entity the entity to put in the merkle tree + * @param merkleTree the merkle tree to put the entity in */ - @Test - public void testManipulatedProof() { - MerkleTree merkleTree = MerkleTreeFixture.createMerkleTree(5); + public static void testManipulatedProof(Entity entity, MerkleTree merkleTree) { + entity = entity != null ? entity : new EntityFixture(); + merkleTree = merkleTree != null ? 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()); + 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. From 705af5c0ed8f2d9c80866d24067d6ba4a90ad698 Mon Sep 17 00:00:00 2001 From: Ozan Nacitarhan Date: Mon, 25 Apr 2022 00:00:06 +0300 Subject: [PATCH 03/12] imlements on-disk Merkle Tree --- .../modules/ads/merkletree/MerkleTree.java | 6 - src/main/java/storage/Entities.java | 38 ++++ src/main/java/storage/MerkleTrees.java | 12 -- src/main/java/storage/mapdb/EntityMapDb.java | 105 +++++++++++ .../java/storage/mapdb/MerkleNodeMapDb.java | 23 ++- .../java/storage/mapdb/MerkleTreeMapDb.java | 175 +++++++++++++----- src/test/java/modules/ads/MerkleTreeTest.java | 2 +- src/test/java/storage/MerkleTreesTest.java | 118 +++++++++--- 8 files changed, 384 insertions(+), 95 deletions(-) create mode 100644 src/main/java/storage/Entities.java delete mode 100644 src/main/java/storage/MerkleTrees.java create mode 100644 src/main/java/storage/mapdb/EntityMapDb.java diff --git a/src/main/java/modules/ads/merkletree/MerkleTree.java b/src/main/java/modules/ads/merkletree/MerkleTree.java index b3cfc061..1a28dace 100644 --- a/src/main/java/modules/ads/merkletree/MerkleTree.java +++ b/src/main/java/modules/ads/merkletree/MerkleTree.java @@ -4,13 +4,11 @@ import java.io.IOException; import java.io.ObjectOutputStream; import java.io.Serializable; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.concurrent.locks.ReentrantReadWriteLock; -import com.google.gson.Gson; import crypto.Sha3256Hasher; import model.Entity; import model.crypto.Sha3256Hash; @@ -152,10 +150,6 @@ public byte[] getBytes() { } catch (IOException e) { e.printStackTrace(); } - /* - Gson gson = new Gson(); - byte[] bytes = gson.toJson(this).getBytes(StandardCharsets.UTF_8); - */ return null; } } diff --git a/src/main/java/storage/Entities.java b/src/main/java/storage/Entities.java new file mode 100644 index 00000000..ffecaf4e --- /dev/null +++ b/src/main/java/storage/Entities.java @@ -0,0 +1,38 @@ +package storage; + +import java.util.ArrayList; + +import model.Entity; + +public interface Entities { + /** + * Adds an entity to the storage, returns true if it is new, false if it already exists. + * + * @param entity entity to be added to storage. + * @return true if it is new, false if it already exists. + */ + boolean add(Entity entity); + + /** + * Checks existence of an entity on the storage. + * + * @param entity entity to be checked. + * @return true if entity exists on the storage, false otherwise. + */ + boolean has(Entity entity); + + /** + * Removes an entity from the storage. + * + * @param entity entity to be removed. + * @return true if entity exists and removed, false otherwise. + */ + boolean remove(Entity entity); + + /** + * Returns all stored entity on storage. + * + * @return all stored entity on storage. + */ + ArrayList all(); +} diff --git a/src/main/java/storage/MerkleTrees.java b/src/main/java/storage/MerkleTrees.java deleted file mode 100644 index 1c479f48..00000000 --- a/src/main/java/storage/MerkleTrees.java +++ /dev/null @@ -1,12 +0,0 @@ -package storage; - -import java.util.ArrayList; - -import modules.ads.merkletree.MerkleTree; - -public interface MerkleTrees { - boolean add(MerkleTree tree); - boolean has(MerkleTree tree); - boolean remove(MerkleTree tree); - ArrayList all(); -} diff --git a/src/main/java/storage/mapdb/EntityMapDb.java b/src/main/java/storage/mapdb/EntityMapDb.java new file mode 100644 index 00000000..21787770 --- /dev/null +++ b/src/main/java/storage/mapdb/EntityMapDb.java @@ -0,0 +1,105 @@ +package storage.mapdb; + +import java.io.*; +import java.util.ArrayList; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import model.Entity; +import org.mapdb.DB; +import org.mapdb.DBMaker; +import org.mapdb.HTreeMap; +import org.mapdb.Serializer; +import storage.Entities; + +public class EntityMapDb implements Entities { + + private final DB db; + private final ReentrantReadWriteLock lock; + private static final String MAP_NAME = "entity_map"; + private final HTreeMap entityMap; + + public EntityMapDb(String filePath) { + this.db = DBMaker.fileDB(filePath).make(); + this.lock = new ReentrantReadWriteLock(); + this.entityMap = db.hashMap(MAP_NAME). + keySerializer(Serializer.BYTE_ARRAY) + .valueSerializer(Serializer.BYTE_ARRAY) + .createOrOpen(); + } + + @Override + public boolean add(Entity entity) { + boolean addBoolean; + try { + lock.writeLock().lock(); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ObjectOutputStream out = new ObjectOutputStream(bos); + out.writeObject(entity); + out.flush(); + byte[] entityBytes = bos.toByteArray(); + addBoolean = entityMap.putIfAbsentBoolean(entityBytes, entityBytes); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + lock.writeLock().unlock(); + } + return addBoolean; + } + + @Override + public boolean has(Entity entity) { + boolean hasBoolean; + try { + lock.readLock().lock(); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ObjectOutputStream out = new ObjectOutputStream(bos); + out.writeObject(entity); + out.flush(); + byte[] entityBytes = bos.toByteArray(); + hasBoolean = entityMap.containsKey(entityBytes); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + lock.readLock().unlock(); + } + return hasBoolean; + } + + @Override + public boolean remove(Entity entity) { + byte[] entityBytes; + try { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ObjectOutputStream out = new ObjectOutputStream(bos); + out.writeObject(entity); + out.flush(); + entityBytes = bos.toByteArray(); + } catch (IOException e) { + throw new RuntimeException(e); + } + return entityMap.remove(entityBytes, entityBytes); + } + + @Override + public ArrayList all() { + ArrayList entities = new ArrayList<>(); + for (byte[] element : entityMap.keySet()) { + try { + ByteArrayInputStream bis = new ByteArrayInputStream(element); + ObjectInputStream inp = null; + inp = new ObjectInputStream(bis); + Entity entity = (Entity) inp.readObject(); + entities.add(entity); + } catch (IOException e) { + e.printStackTrace(); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + return entities; + } + + public void closeDb() { + db.close(); + } +} diff --git a/src/main/java/storage/mapdb/MerkleNodeMapDb.java b/src/main/java/storage/mapdb/MerkleNodeMapDb.java index 2c7f748e..8f8f255d 100644 --- a/src/main/java/storage/mapdb/MerkleNodeMapDb.java +++ b/src/main/java/storage/mapdb/MerkleNodeMapDb.java @@ -1,10 +1,14 @@ package storage.mapdb; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.ObjectInputStream; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.concurrent.locks.ReentrantReadWriteLock; import com.google.gson.Gson; +import model.Entity; import modules.ads.merkletree.MerkleNode; import org.mapdb.DB; import org.mapdb.DBMaker; @@ -60,11 +64,22 @@ public boolean remove(MerkleNode node) { public ArrayList all() { ArrayList nodes = new ArrayList<>(); for (byte[] element : merkleNodeMap.keySet()) { - Gson gson = new Gson(); - String json = new String(element.clone(), StandardCharsets.UTF_8); - MerkleNode node = gson.fromJson(json, MerkleNode.class); - nodes.add(node); + try { + ByteArrayInputStream bis = new ByteArrayInputStream(element); + ObjectInputStream inp = null; + inp = new ObjectInputStream(bis); + MerkleNode node = (MerkleNode) inp.readObject(); + nodes.add(node); + } catch (IOException e) { + e.printStackTrace(); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } } return nodes; } + + public void closeDb() { + db.close(); + } } diff --git a/src/main/java/storage/mapdb/MerkleTreeMapDb.java b/src/main/java/storage/mapdb/MerkleTreeMapDb.java index 8d3189da..a1ff06c0 100644 --- a/src/main/java/storage/mapdb/MerkleTreeMapDb.java +++ b/src/main/java/storage/mapdb/MerkleTreeMapDb.java @@ -1,87 +1,164 @@ package storage.mapdb; import java.io.*; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.locks.ReentrantReadWriteLock; -import com.google.gson.Gson; +import crypto.Sha3256Hasher; +import model.Entity; +import model.crypto.Sha3256Hash; +import model.lightchain.Identifier; +import modules.ads.merkletree.MerkleNode; +import modules.ads.merkletree.MerkleProof; import modules.ads.merkletree.MerkleTree; -import org.mapdb.DB; -import org.mapdb.DBMaker; -import org.mapdb.HTreeMap; -import org.mapdb.Serializer; -import storage.MerkleTrees; +import modules.ads.merkletree.MerkleTreeAuthenticatedEntity; -public class MerkleTreeMapDb implements MerkleTrees { - private final DB db; +public class MerkleTreeMapDb extends MerkleTree { + private final MerkleNodeMapDb merkleNodeMapDb; + private final EntityMapDb entityMapDb; + private static final Sha3256Hasher hasher = new Sha3256Hasher(); private final ReentrantReadWriteLock lock; - private static final String MAP_NAME = "merkle_tree_map"; - private final HTreeMap merkleTreeMap; + private final ArrayList leafNodes; + private final Map leafNodesHashTable; + private final Map entityHashTable; + private int size; + private MerkleNode root; - public MerkleTreeMapDb(String filePath) { - this.db = DBMaker.fileDB(filePath).make(); + public MerkleTreeMapDb(MerkleNodeMapDb merkleNodeMapDb, EntityMapDb entityMapDb) { + this.merkleNodeMapDb = merkleNodeMapDb; + this.entityMapDb = entityMapDb; + this.size = 0; + this.root = new MerkleNode(); + this.leafNodes = new ArrayList<>(); this.lock = new ReentrantReadWriteLock(); - this.merkleTreeMap = db.hashMap(MAP_NAME). - keySerializer(Serializer.BYTE_ARRAY) - .valueSerializer(Serializer.BYTE_ARRAY) - .createOrOpen(); + this.leafNodesHashTable = new HashMap<>(); + this.entityHashTable = new HashMap<>(); } @Override - public boolean add(MerkleTree tree) { - boolean addBoolean; + public modules.ads.AuthenticatedEntity put(Entity e) throws IllegalArgumentException { try { lock.writeLock().lock(); - addBoolean = merkleTreeMap.putIfAbsentBoolean(tree.getBytes(), tree.getBytes()); + if (!entityMapDb.has(e)) { + entityMapDb.add(e); + } + 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) { + MerkleNode newNode = new MerkleNode(e, false); + if (!merkleNodeMapDb.has(newNode)) { + merkleNodeMapDb.add(newNode); + } + leafNodes.add(newNode); + 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(); } - return addBoolean; } @Override - public boolean has(MerkleTree tree) { - boolean hasBoolean; + 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(); - hasBoolean = merkleTreeMap.containsKey(tree.getBytes()); + proof = getProof(id); + Entity e = entityHashTable.get(id); + return new MerkleTreeAuthenticatedEntity(proof, e.type(), e); } finally { lock.readLock().unlock(); } - return hasBoolean; } - @Override - public boolean remove(MerkleTree tree) { - return merkleTreeMap.remove(tree.getBytes(), tree.getBytes()); + 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); } - @Override - public ArrayList all() { - ArrayList trees = new ArrayList<>(); - for (byte[] element : merkleTreeMap.keySet()) { - try { - ByteArrayInputStream bis = new ByteArrayInputStream(element); - ObjectInputStream inp = null; - inp = new ObjectInputStream(bis); - MerkleTree tree = (MerkleTree) inp.readObject(); - /* - Gson gson = new Gson(); - String json = new String(element.clone(), StandardCharsets.UTF_8); - MerkleTree tree = gson.fromJson(json, MerkleTree.class); - */ - trees.add(tree); - } catch (IOException e) { - e.printStackTrace(); - } catch (ClassNotFoundException e) { - throw new RuntimeException(e); + 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; + } + + public byte[] getBytes() { + try { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ObjectOutputStream out = null; + out = new ObjectOutputStream(bos); + out.writeObject(this); + out.flush(); + byte[] bytes = bos.toByteArray(); + return bytes; + } catch (IOException e) { + e.printStackTrace(); } - return trees; + return null; } public void closeDb() { - db.close(); + merkleNodeMapDb.closeDb(); + entityMapDb.closeDb(); } } diff --git a/src/test/java/modules/ads/MerkleTreeTest.java b/src/test/java/modules/ads/MerkleTreeTest.java index 217f724f..3c56b63f 100644 --- a/src/test/java/modules/ads/MerkleTreeTest.java +++ b/src/test/java/modules/ads/MerkleTreeTest.java @@ -200,7 +200,7 @@ public static void testConcurrentPutGet(String type, MerkleTree merkleTree) { for (int i = 0; i < concurrencyDegree; i++) { Entity entity; - if (type.equals(EntityType.TYPE_ACCOUNT)) { + if (type == EntityType.TYPE_ACCOUNT) { entity = AccountFixture.newAccount(IdentifierFixture.newIdentifier()); } else { entity = new EntityFixture(); diff --git a/src/test/java/storage/MerkleTreesTest.java b/src/test/java/storage/MerkleTreesTest.java index 3f5d9813..8bec875f 100644 --- a/src/test/java/storage/MerkleTreesTest.java +++ b/src/test/java/storage/MerkleTreesTest.java @@ -9,22 +9,31 @@ import model.Entity; import modules.ads.AuthenticatedEntity; +import modules.ads.MerkleTreeTest; import modules.ads.merkletree.MerkleTree; import modules.ads.merkletree.MerkleTreeAuthenticatedEntityVerifier; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.testcontainers.shaded.org.apache.commons.io.FileUtils; +import storage.mapdb.EntityMapDb; +import storage.mapdb.MerkleNodeMapDb; import storage.mapdb.MerkleTreeMapDb; import unittest.fixtures.EntityFixture; import unittest.fixtures.MerkleTreeFixture; public class MerkleTreesTest { - private static final String TEMP_DIR = "tempdir"; - private static final String TEMP_FILE = "tempfile.db"; - private Path tempdir; + private static final String TEMP_DIR_1 = "tempdir1"; + private static final String TEMP_FILE_1 = "tempfile1.db"; + private static final String TEMP_DIR_2 = "tempdir2"; + private static final String TEMP_FILE_2 = "tempfile2.db"; + private Path tempdirNode; + private Path tempdirEntity; private ArrayList allTrees; - private MerkleTreeMapDb db; + private MerkleNodeMapDb dbNode; + private EntityMapDb dbEntity; + private MerkleTreeMapDb dbMerkleTree; /** * Sets up the tests. @@ -34,32 +43,95 @@ public class MerkleTreesTest { @BeforeEach void setUp() throws IOException { Path currentRelativePath = Paths.get(""); - tempdir = Files.createTempDirectory(currentRelativePath, TEMP_DIR); - db = new MerkleTreeMapDb(tempdir.toAbsolutePath() + "/" + TEMP_FILE); - allTrees = MerkleTreeFixture.newMerkleTrees(10, 5); + tempdirNode = Files.createTempDirectory(currentRelativePath, TEMP_DIR_1); + tempdirEntity = Files.createTempDirectory(currentRelativePath, TEMP_DIR_2); + dbNode = new MerkleNodeMapDb(tempdirEntity.toAbsolutePath() + "/" + TEMP_FILE_1); + dbEntity = new EntityMapDb(tempdirEntity.toAbsolutePath() + "/" + TEMP_FILE_2); + dbMerkleTree = new MerkleTreeMapDb(dbNode, dbEntity); + } + + @AfterEach + void tearDown() throws IOException { + dbNode.closeDb(); + dbEntity.closeDb(); + dbMerkleTree.closeDb(); + FileUtils.deleteDirectory(tempdirNode.toFile()); + FileUtils.deleteDirectory(tempdirEntity.toFile()); + } + @Test + public void testVerification() { + for (int i = 0; i < 5; i++) { + Entity entity = new EntityFixture(); + dbMerkleTree.put(entity); + } + MerkleTreeTest.testVerification(null, dbMerkleTree); + } + + @Test + public void testPutGetSameProof() { + for (int i = 0; i < 5; i++) { + Entity entity = new EntityFixture(); + dbMerkleTree.put(entity); + } + MerkleTreeTest.testPutGetSameProof(null, dbMerkleTree); + } + + @Test + public void testPutExistingEntity() { + for (int i = 0; i < 5; i++) { + Entity entity = new EntityFixture(); + dbMerkleTree.put(entity); + } + MerkleTreeTest.testPutExistingEntity(null, dbMerkleTree); + } + + @Test + public void testConcurrentPutGet() { + MerkleTreeTest.testConcurrentPutGet(null, dbMerkleTree); } @Test - public void testVerification() throws IOException { - for (MerkleTree tree : allTrees) { - Assertions.assertTrue(db.add(tree)); + public void testGetNonExistingEntity() { + for (int i = 0; i < 5; i++) { + Entity entity = new EntityFixture(); + dbMerkleTree.put(entity); } - for (MerkleTree tree : allTrees) { - Assertions.assertTrue(db.has(tree)); + MerkleTreeTest.testGetNonExistingEntity(null, dbMerkleTree); + } + + @Test + public void testNullInsertion() { + for (int i = 0; i < 5; i++) { + Entity entity = new EntityFixture(); + dbMerkleTree.put(entity); + } + MerkleTreeTest.testNullInsertion(dbMerkleTree); + } + + @Test + public void testManipulatedRoot() { + for (int i = 0; i < 5; i++) { + Entity entity = new EntityFixture(); + dbMerkleTree.put(entity); } - ArrayList all = db.all(); - Assertions.assertEquals(all.size(), 10); - for (MerkleTree merkleTree : all) { - Assertions.assertEquals(merkleTree.size(), 5); // fixture sanity check. + MerkleTreeTest.testManipulatedRoot(null, dbMerkleTree); + } + + @Test + public void testManipulatedEntity() { + for (int i = 0; i < 5; i++) { 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)); + dbMerkleTree.put(entity); } - db.closeDb(); - FileUtils.deleteDirectory(new File(tempdir.toString())); + MerkleTreeTest.testManipulatedEntity(null, dbMerkleTree); } + @Test + public void testManipulatedProof() { + for (int i = 0; i < 5; i++) { + Entity entity = new EntityFixture(); + dbMerkleTree.put(entity); + } + MerkleTreeTest.testManipulatedProof(null, dbMerkleTree); + } } From 53fed0cdd233c4262c6adb3add4b740b1db77029 Mon Sep 17 00:00:00 2001 From: Ozan Date: Tue, 26 Apr 2022 18:04:26 +0300 Subject: [PATCH 04/12] implements MerkleTreeState and its MapDb to later use in persistent merkle tree --- src/main/java/modules/ads/MerkleTree.java | 20 + .../modules/ads/merkletree/MerkleNode.java | 14 + ...Tree.java => MerkleTreeInMemoryState.java} | 36 +- .../ads/merkletree/MerkleTreeState.java | 99 +++++ src/main/java/modules/codec/JsonEncoder.java | 34 +- src/main/java/storage/Entities.java | 38 -- src/main/java/storage/MerkleNodes.java | 12 - src/main/java/storage/MerkleTreeStates.java | 40 ++ src/main/java/storage/mapdb/EntityMapDb.java | 105 ----- .../java/storage/mapdb/MerkleNodeMapDb.java | 85 ---- .../java/storage/mapdb/MerkleTreeMapDb.java | 164 -------- .../storage/mapdb/MerkleTreeStateMapDb.java | 115 ++++++ src/test/java/modules/ads/MerkleTreeTest.java | 26 +- src/test/java/storage/IdentifiersTest.java | 2 + .../java/storage/MerkleTreeStatesTest.java | 385 ++++++++++++++++++ src/test/java/storage/MerkleTreesTest.java | 137 ------- .../unittest/fixtures/MerkleTreeFixture.java | 11 +- .../fixtures/MerkleTreeStateFixture.java | 25 ++ 18 files changed, 762 insertions(+), 586 deletions(-) create mode 100644 src/main/java/modules/ads/MerkleTree.java rename src/main/java/modules/ads/merkletree/{MerkleTree.java => MerkleTreeInMemoryState.java} (83%) create mode 100644 src/main/java/modules/ads/merkletree/MerkleTreeState.java delete mode 100644 src/main/java/storage/Entities.java delete mode 100644 src/main/java/storage/MerkleNodes.java create mode 100644 src/main/java/storage/MerkleTreeStates.java delete mode 100644 src/main/java/storage/mapdb/EntityMapDb.java delete mode 100644 src/main/java/storage/mapdb/MerkleNodeMapDb.java delete mode 100644 src/main/java/storage/mapdb/MerkleTreeMapDb.java create mode 100644 src/main/java/storage/mapdb/MerkleTreeStateMapDb.java create mode 100644 src/test/java/storage/MerkleTreeStatesTest.java delete mode 100644 src/test/java/storage/MerkleTreesTest.java create mode 100644 src/test/java/unittest/fixtures/MerkleTreeStateFixture.java diff --git a/src/main/java/modules/ads/MerkleTree.java b/src/main/java/modules/ads/MerkleTree.java new file mode 100644 index 00000000..e4d9df0e --- /dev/null +++ b/src/main/java/modules/ads/MerkleTree.java @@ -0,0 +1,20 @@ +package modules.ads; + +import model.Entity; +import model.lightchain.Identifier; +import modules.ads.merkletree.MerkleProof; + +public interface MerkleTree { + default modules.ads.AuthenticatedEntity put(Entity e) throws IllegalArgumentException{ + return null; + } + default modules.ads.AuthenticatedEntity get(Identifier id) throws IllegalArgumentException{ + return null; + } + default MerkleProof getProof(Identifier id) throws IllegalArgumentException{ + return null; + } + void buildMerkleTree(); + int size(); + byte[] getBytes(); +} diff --git a/src/main/java/modules/ads/merkletree/MerkleNode.java b/src/main/java/modules/ads/merkletree/MerkleNode.java index 10548590..6962a104 100644 --- a/src/main/java/modules/ads/merkletree/MerkleNode.java +++ b/src/main/java/modules/ads/merkletree/MerkleNode.java @@ -2,6 +2,7 @@ import java.io.Serializable; import java.nio.charset.StandardCharsets; +import java.util.Objects; import com.google.gson.Gson; import crypto.Sha3256Hasher; @@ -119,6 +120,19 @@ public MerkleNode getSibling() { } } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MerkleNode that = (MerkleNode) o; + return hash.equals(that.hash); + } + + @Override + public int hashCode() { + return Objects.hash(left, right, parent, isLeft, hash); + } + public byte[] getBytes() { Gson gson = new Gson(); byte[] bytes = gson.toJson(this).getBytes(StandardCharsets.UTF_8); diff --git a/src/main/java/modules/ads/merkletree/MerkleTree.java b/src/main/java/modules/ads/merkletree/MerkleTreeInMemoryState.java similarity index 83% rename from src/main/java/modules/ads/merkletree/MerkleTree.java rename to src/main/java/modules/ads/merkletree/MerkleTreeInMemoryState.java index 1a28dace..165889f7 100644 --- a/src/main/java/modules/ads/merkletree/MerkleTree.java +++ b/src/main/java/modules/ads/merkletree/MerkleTreeInMemoryState.java @@ -19,25 +19,21 @@ * Implementation of an in-memory Authenticated Skip List * that is capable of storing and retrieval of LightChain entities. */ -public class MerkleTree implements AuthenticatedDataStructure, Serializable { +public class MerkleTreeInMemoryState implements AuthenticatedDataStructure, Serializable { private static final Sha3256Hasher hasher = new Sha3256Hasher(); private final ReentrantReadWriteLock lock; - private final ArrayList leafNodes; - private final Map leafNodesHashTable; - private final Map entityHashTable; + private final MerkleTreeState state; private int size; private MerkleNode root; /** * Default constructor for a Merkle Tree. */ - public MerkleTree() { + public MerkleTreeInMemoryState() { this.size = 0; this.root = new MerkleNode(); - this.leafNodes = new ArrayList<>(); this.lock = new ReentrantReadWriteLock(); - this.leafNodesHashTable = new HashMap<>(); - this.entityHashTable = new HashMap<>(); + this.state = new MerkleTreeState(); } @Override @@ -48,11 +44,11 @@ public modules.ads.AuthenticatedEntity put(Entity e) throws IllegalArgumentExcep 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); + int idx = state.getNodeIndex(hash); + if (idx == -1) { + state.addLeafNode(new MerkleNode(e, false)); + state.putLeafNodeHash(hash, size); + state.putEntityHashTable(e.id(), e); size++; buildMerkleTree(); MerkleProof proof = getProof(e.id()); @@ -75,7 +71,7 @@ public modules.ads.AuthenticatedEntity get(Identifier id) throws IllegalArgument try { lock.readLock().lock(); proof = getProof(id); - Entity e = entityHashTable.get(id); + Entity e = state.getEntity(id); return new MerkleTreeAuthenticatedEntity(proof, e.type(), e); } finally { lock.readLock().unlock(); @@ -85,12 +81,12 @@ public modules.ads.AuthenticatedEntity get(Identifier id) throws IllegalArgument 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) { + int idx = state.getNodeIndex(hash); + if (idx == -1) { throw new IllegalArgumentException("identifier not found"); } ArrayList path = new ArrayList<>(); - MerkleNode currentNode = leafNodes.get(idx); + MerkleNode currentNode = state.getNode(idx); while (currentNode != root) { path.add(currentNode.getSibling().getHash()); isLeftNode.add(currentNode.isLeft()); @@ -103,7 +99,7 @@ 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); + ArrayList currentLevelNodes = new ArrayList<>(state.getLeafNodes()); // keeps nodes of the next level of merkle tree // used as an intermediary data structure. @@ -152,4 +148,8 @@ public byte[] getBytes() { } return null; } + + public MerkleTreeState getState() { + return state; + } } diff --git a/src/main/java/modules/ads/merkletree/MerkleTreeState.java b/src/main/java/modules/ads/merkletree/MerkleTreeState.java new file mode 100644 index 00000000..6daf34c2 --- /dev/null +++ b/src/main/java/modules/ads/merkletree/MerkleTreeState.java @@ -0,0 +1,99 @@ +package modules.ads.merkletree; + +import model.Entity; +import model.crypto.Sha3256Hash; +import model.lightchain.Identifier; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.*; + +public class MerkleTreeState implements Serializable { + private ArrayList leafNodes; + private Map leafNodesHashTable; + private Map entityHashTable; + + public MerkleTreeState() { + this.leafNodes = new ArrayList<>(); + this.leafNodesHashTable = new HashMap<>(); + this.entityHashTable = new HashMap<>(); + } + + public MerkleTreeState(ArrayList leafNodes, Map leafNodesHashTable, Map entityHashTable) { + this.leafNodes = leafNodes; + this.leafNodesHashTable = leafNodesHashTable; + this.entityHashTable = entityHashTable; + } + + public ArrayList getLeafNodes() { + return leafNodes; + } + + public Map getLeafNodesHashTable() { + return leafNodesHashTable; + } + + public Map getEntityHashTable() { + return entityHashTable; + } + + public void addLeafNode(MerkleNode node) { + this.leafNodes.add(node); + } + + public void putLeafNodeHash(Sha3256Hash hash, Integer idx) { + this.leafNodesHashTable.put(hash, idx); + } + + public void putEntityHashTable(Identifier id, Entity e) { + this.entityHashTable.put(id, e); + } + + public int getNodeIndex(Sha3256Hash hash) { + if (this.leafNodesHashTable.get(hash) != null) { + return this.leafNodesHashTable.get(hash); + } + return -1; + } + + public Entity getEntity(Identifier id) { + if (this.entityHashTable.get(id) != null) { + return this.entityHashTable.get(id); + } + return null; + } + + public MerkleNode getNode(int idx) { + return this.leafNodes.get(idx); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MerkleTreeState that = (MerkleTreeState) o; + return leafNodes.equals(that.leafNodes); + } + + @Override + public int hashCode() { + return Objects.hash(leafNodes); + } + + public byte[] getBytes() { + try { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ObjectOutputStream out = null; + out = new ObjectOutputStream(bos); + out.writeObject(this); + out.flush(); + byte[] bytes = bos.toByteArray(); + return bytes; + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } +} diff --git a/src/main/java/modules/codec/JsonEncoder.java b/src/main/java/modules/codec/JsonEncoder.java index d5d3191f..b7399f0e 100644 --- a/src/main/java/modules/codec/JsonEncoder.java +++ b/src/main/java/modules/codec/JsonEncoder.java @@ -1,5 +1,6 @@ package modules.codec; +import java.io.*; import java.lang.reflect.Type; import java.nio.charset.StandardCharsets; @@ -10,7 +11,7 @@ /** * Implements encoding and decoding using JSON. */ -public class JsonEncoder implements Codec { +public class JsonEncoder implements Codec, Serializable { /** * Encodes an Entity to an EncodedEntity. * @@ -19,8 +20,17 @@ public class JsonEncoder implements Codec { */ @Override public EncodedEntity encode(Entity e) { - Gson gson = new Gson(); - byte[] bytes = gson.toJson(e).getBytes(StandardCharsets.UTF_8); + byte[] bytes = new byte[0]; + try { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ObjectOutputStream out = null; + out = new ObjectOutputStream(bos); + out.writeObject(e); + out.flush(); + bytes = bos.toByteArray(); + } catch (IOException ex) { + ex.printStackTrace(); + } String type = e.getClass().getCanonicalName(); return new EncodedEntity(bytes, type); } @@ -33,9 +43,17 @@ public EncodedEntity encode(Entity e) { */ @Override public Entity decode(EncodedEntity e) throws ClassNotFoundException { - Gson gson = new Gson(); - String json = new String(e.getBytes().clone(), StandardCharsets.UTF_8); - return gson.fromJson(json, (Type) Class.forName(e.getType())); - + Entity entity = null; + try { + ByteArrayInputStream bis = new ByteArrayInputStream(e.getBytes().clone()); + ObjectInputStream inp = null; + inp = new ObjectInputStream(bis); + entity = (Entity) (Class.forName(e.getType())).cast(inp.readObject()); + } catch (IOException ex) { + ex.printStackTrace(); + } catch (ClassNotFoundException ex) { + throw new RuntimeException(ex); + } + return entity; } -} +} \ No newline at end of file diff --git a/src/main/java/storage/Entities.java b/src/main/java/storage/Entities.java deleted file mode 100644 index ffecaf4e..00000000 --- a/src/main/java/storage/Entities.java +++ /dev/null @@ -1,38 +0,0 @@ -package storage; - -import java.util.ArrayList; - -import model.Entity; - -public interface Entities { - /** - * Adds an entity to the storage, returns true if it is new, false if it already exists. - * - * @param entity entity to be added to storage. - * @return true if it is new, false if it already exists. - */ - boolean add(Entity entity); - - /** - * Checks existence of an entity on the storage. - * - * @param entity entity to be checked. - * @return true if entity exists on the storage, false otherwise. - */ - boolean has(Entity entity); - - /** - * Removes an entity from the storage. - * - * @param entity entity to be removed. - * @return true if entity exists and removed, false otherwise. - */ - boolean remove(Entity entity); - - /** - * Returns all stored entity on storage. - * - * @return all stored entity on storage. - */ - ArrayList all(); -} diff --git a/src/main/java/storage/MerkleNodes.java b/src/main/java/storage/MerkleNodes.java deleted file mode 100644 index 24dc668c..00000000 --- a/src/main/java/storage/MerkleNodes.java +++ /dev/null @@ -1,12 +0,0 @@ -package storage; - -import java.util.ArrayList; - -import modules.ads.merkletree.MerkleNode; - -public interface MerkleNodes { - boolean add(MerkleNode node); - boolean has(MerkleNode node); - boolean remove(MerkleNode node); - ArrayList all(); -} diff --git a/src/main/java/storage/MerkleTreeStates.java b/src/main/java/storage/MerkleTreeStates.java new file mode 100644 index 00000000..1da10a50 --- /dev/null +++ b/src/main/java/storage/MerkleTreeStates.java @@ -0,0 +1,40 @@ +package storage; + +import model.lightchain.Identifier; +import modules.ads.merkletree.MerkleTreeState; + +import java.util.ArrayList; + +public interface MerkleTreeStates { + + /** + * Adds a merkle tree state to the storage, returns true if it is new, false if it already exists. + * + * @param merkleTreeState merkle tree state to be added to storage. + * @return true if it is new, false if it already exists. + */ + boolean add(MerkleTreeState merkleTreeState); + + /** + * Checks existence of a merkle tree state on the storage. + * + * @param merkleTreeState merkle tree state to be checked. + * @return true if it exists on the storage, false otherwise. + */ + boolean has(MerkleTreeState merkleTreeState); + + /** + * Removes a merkle tree state from the storage. + * + * @param merkleTreeState merkle tree state to be removed. + * @return true if it exists and removed, false otherwise. + */ + boolean remove(MerkleTreeState merkleTreeState); + + /** + * Returns all stored merkle tree state on storage. + * + * @return all stored merkle tree state on storage. + */ + ArrayList all(); +} diff --git a/src/main/java/storage/mapdb/EntityMapDb.java b/src/main/java/storage/mapdb/EntityMapDb.java deleted file mode 100644 index 21787770..00000000 --- a/src/main/java/storage/mapdb/EntityMapDb.java +++ /dev/null @@ -1,105 +0,0 @@ -package storage.mapdb; - -import java.io.*; -import java.util.ArrayList; -import java.util.concurrent.locks.ReentrantReadWriteLock; - -import model.Entity; -import org.mapdb.DB; -import org.mapdb.DBMaker; -import org.mapdb.HTreeMap; -import org.mapdb.Serializer; -import storage.Entities; - -public class EntityMapDb implements Entities { - - private final DB db; - private final ReentrantReadWriteLock lock; - private static final String MAP_NAME = "entity_map"; - private final HTreeMap entityMap; - - public EntityMapDb(String filePath) { - this.db = DBMaker.fileDB(filePath).make(); - this.lock = new ReentrantReadWriteLock(); - this.entityMap = db.hashMap(MAP_NAME). - keySerializer(Serializer.BYTE_ARRAY) - .valueSerializer(Serializer.BYTE_ARRAY) - .createOrOpen(); - } - - @Override - public boolean add(Entity entity) { - boolean addBoolean; - try { - lock.writeLock().lock(); - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - ObjectOutputStream out = new ObjectOutputStream(bos); - out.writeObject(entity); - out.flush(); - byte[] entityBytes = bos.toByteArray(); - addBoolean = entityMap.putIfAbsentBoolean(entityBytes, entityBytes); - } catch (IOException e) { - throw new RuntimeException(e); - } finally { - lock.writeLock().unlock(); - } - return addBoolean; - } - - @Override - public boolean has(Entity entity) { - boolean hasBoolean; - try { - lock.readLock().lock(); - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - ObjectOutputStream out = new ObjectOutputStream(bos); - out.writeObject(entity); - out.flush(); - byte[] entityBytes = bos.toByteArray(); - hasBoolean = entityMap.containsKey(entityBytes); - } catch (IOException e) { - throw new RuntimeException(e); - } finally { - lock.readLock().unlock(); - } - return hasBoolean; - } - - @Override - public boolean remove(Entity entity) { - byte[] entityBytes; - try { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - ObjectOutputStream out = new ObjectOutputStream(bos); - out.writeObject(entity); - out.flush(); - entityBytes = bos.toByteArray(); - } catch (IOException e) { - throw new RuntimeException(e); - } - return entityMap.remove(entityBytes, entityBytes); - } - - @Override - public ArrayList all() { - ArrayList entities = new ArrayList<>(); - for (byte[] element : entityMap.keySet()) { - try { - ByteArrayInputStream bis = new ByteArrayInputStream(element); - ObjectInputStream inp = null; - inp = new ObjectInputStream(bis); - Entity entity = (Entity) inp.readObject(); - entities.add(entity); - } catch (IOException e) { - e.printStackTrace(); - } catch (ClassNotFoundException e) { - throw new RuntimeException(e); - } - } - return entities; - } - - public void closeDb() { - db.close(); - } -} diff --git a/src/main/java/storage/mapdb/MerkleNodeMapDb.java b/src/main/java/storage/mapdb/MerkleNodeMapDb.java deleted file mode 100644 index 8f8f255d..00000000 --- a/src/main/java/storage/mapdb/MerkleNodeMapDb.java +++ /dev/null @@ -1,85 +0,0 @@ -package storage.mapdb; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.concurrent.locks.ReentrantReadWriteLock; - -import com.google.gson.Gson; -import model.Entity; -import modules.ads.merkletree.MerkleNode; -import org.mapdb.DB; -import org.mapdb.DBMaker; -import org.mapdb.HTreeMap; -import org.mapdb.Serializer; -import storage.MerkleNodes; - -public class MerkleNodeMapDb implements MerkleNodes { - private final DB db; - private final ReentrantReadWriteLock lock; - private static final String MAP_NAME = "merkle_node_map"; - private final HTreeMap merkleNodeMap; - - public MerkleNodeMapDb(String filePath) { - this.db = DBMaker.fileDB(filePath).make(); - this.lock = new ReentrantReadWriteLock(); - this.merkleNodeMap = db.hashMap(MAP_NAME). - keySerializer(Serializer.BYTE_ARRAY) - .valueSerializer(Serializer.BYTE_ARRAY) - .createOrOpen(); - } - - @Override - public boolean add(MerkleNode node) { - boolean addBoolean; - try { - lock.writeLock().lock(); - addBoolean = merkleNodeMap.putIfAbsentBoolean(node.getBytes(), node.getBytes()); - } finally { - lock.writeLock().unlock(); - } - return addBoolean; - } - - @Override - public boolean has(MerkleNode node) { - boolean hasBoolean; - try { - lock.readLock().lock(); - hasBoolean = merkleNodeMap.containsKey(node.getBytes()); - } finally { - lock.readLock().unlock(); - } - return hasBoolean; - } - - @Override - public boolean remove(MerkleNode node) { - return merkleNodeMap.remove(node.getBytes(), node.getBytes()); - } - - @Override - public ArrayList all() { - ArrayList nodes = new ArrayList<>(); - for (byte[] element : merkleNodeMap.keySet()) { - try { - ByteArrayInputStream bis = new ByteArrayInputStream(element); - ObjectInputStream inp = null; - inp = new ObjectInputStream(bis); - MerkleNode node = (MerkleNode) inp.readObject(); - nodes.add(node); - } catch (IOException e) { - e.printStackTrace(); - } catch (ClassNotFoundException e) { - throw new RuntimeException(e); - } - } - return nodes; - } - - public void closeDb() { - db.close(); - } -} diff --git a/src/main/java/storage/mapdb/MerkleTreeMapDb.java b/src/main/java/storage/mapdb/MerkleTreeMapDb.java deleted file mode 100644 index a1ff06c0..00000000 --- a/src/main/java/storage/mapdb/MerkleTreeMapDb.java +++ /dev/null @@ -1,164 +0,0 @@ -package storage.mapdb; - -import java.io.*; -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.merkletree.MerkleNode; -import modules.ads.merkletree.MerkleProof; -import modules.ads.merkletree.MerkleTree; -import modules.ads.merkletree.MerkleTreeAuthenticatedEntity; - -public class MerkleTreeMapDb extends MerkleTree { - private final MerkleNodeMapDb merkleNodeMapDb; - private final EntityMapDb entityMapDb; - 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; - - public MerkleTreeMapDb(MerkleNodeMapDb merkleNodeMapDb, EntityMapDb entityMapDb) { - this.merkleNodeMapDb = merkleNodeMapDb; - this.entityMapDb = entityMapDb; - 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 (!entityMapDb.has(e)) { - entityMapDb.add(e); - } - 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) { - MerkleNode newNode = new MerkleNode(e, false); - if (!merkleNodeMapDb.has(newNode)) { - merkleNodeMapDb.add(newNode); - } - leafNodes.add(newNode); - 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; - } - - public byte[] getBytes() { - try { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - ObjectOutputStream out = null; - out = new ObjectOutputStream(bos); - out.writeObject(this); - out.flush(); - byte[] bytes = bos.toByteArray(); - return bytes; - } catch (IOException e) { - e.printStackTrace(); - } - return null; - } - - public void closeDb() { - merkleNodeMapDb.closeDb(); - entityMapDb.closeDb(); - } -} diff --git a/src/main/java/storage/mapdb/MerkleTreeStateMapDb.java b/src/main/java/storage/mapdb/MerkleTreeStateMapDb.java new file mode 100644 index 00000000..ea37dede --- /dev/null +++ b/src/main/java/storage/mapdb/MerkleTreeStateMapDb.java @@ -0,0 +1,115 @@ +package storage.mapdb; + +import model.lightchain.Identifier; +import modules.ads.merkletree.MerkleTreeState; +import org.mapdb.DB; +import org.mapdb.DBMaker; +import org.mapdb.HTreeMap; +import org.mapdb.Serializer; +import storage.MerkleTreeStates; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.util.ArrayList; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +public class MerkleTreeStateMapDb implements MerkleTreeStates { + private final DB db; + private final ReentrantReadWriteLock lock; + private static final String MAP_NAME = "merkle_tree_state_map"; + private final HTreeMap merkleTreeStateMap; + + /** + * Creates MapDb. + * + * @param filePath the path of the file. + */ + public MerkleTreeStateMapDb(String filePath) { + this.db = DBMaker.fileDB(filePath).make(); + this.lock = new ReentrantReadWriteLock(); + merkleTreeStateMap = this.db.hashMap(MAP_NAME) + .keySerializer(Serializer.BYTE_ARRAY) + .valueSerializer(Serializer.BYTE_ARRAY) + .createOrOpen(); + } + + /** + * Adds a merkle tree state to the storage, returns true if it is new, false if it already exists. + * + * @param merkleTreeState merkle tree state to be added to storage. + * @return true if it is new, false if it already exists. + */ + @Override + public boolean add(MerkleTreeState merkleTreeState) { + boolean addBoolean; + try { + lock.writeLock().lock(); + addBoolean = merkleTreeStateMap.putIfAbsentBoolean(merkleTreeState.getBytes(), merkleTreeState.getBytes()); + } finally { + lock.writeLock().unlock(); + } + return addBoolean; + } + + /** + * Checks existence of a merkle tree state on the storage. + * + * @param merkleTreeState merkle tree state to be checked. + * @return true if it exists on the storage, false otherwise. + */ + @Override + public boolean has(MerkleTreeState merkleTreeState) { + boolean hasBoolean; + try { + lock.readLock().lock(); + hasBoolean = merkleTreeStateMap.containsKey(merkleTreeState.getBytes()); + } finally { + lock.readLock().unlock(); + } + return hasBoolean; + } + + /** + * Removes a merkle tree state from the storage. + * + * @param merkleTreeState merkle tree state to be removed. + * @return true if it exists and removed, false otherwise. + */ + @Override + public boolean remove(MerkleTreeState merkleTreeState) { + return merkleTreeStateMap.remove(merkleTreeState.getBytes(), merkleTreeState.getBytes()); + } + + /** + * Returns all stored merkle tree state on storage. + * + * @return all stored merkle tree state on storage. + */ + @Override + public ArrayList all() { + ArrayList states; + states = new ArrayList<>(); + for (byte[] element : merkleTreeStateMap.keySet()) { + try { + ByteArrayInputStream bis = new ByteArrayInputStream(element); + ObjectInputStream inp = null; + inp = new ObjectInputStream(bis); + MerkleTreeState state = (MerkleTreeState) inp.readObject(); + states.add(state); + } catch (IOException e) { + e.printStackTrace(); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + return states; + } + + /** + * Closes the database. + */ + public void closeDb() { + db.close(); + } +} diff --git a/src/test/java/modules/ads/MerkleTreeTest.java b/src/test/java/modules/ads/MerkleTreeTest.java index 3c56b63f..5a31f3f4 100644 --- a/src/test/java/modules/ads/MerkleTreeTest.java +++ b/src/test/java/modules/ads/MerkleTreeTest.java @@ -10,7 +10,7 @@ import model.crypto.Sha3256Hash; import model.lightchain.Identifier; import modules.ads.merkletree.MerkleProof; -import modules.ads.merkletree.MerkleTree; +import modules.ads.merkletree.MerkleTreeInMemoryState; import modules.ads.merkletree.MerkleTreeAuthenticatedEntity; import modules.ads.merkletree.MerkleTreeAuthenticatedEntityVerifier; import org.junit.jupiter.api.Assertions; @@ -102,7 +102,7 @@ public void testManipulatedProofNoArg() { * @param entity the entity to put in the merkle tree * @param merkleTree the merkle tree to put the entity in */ - public static void testVerification(Entity entity, MerkleTree merkleTree) { + public static void testVerification(Entity entity, MerkleTreeInMemoryState merkleTree) { entity = entity != null ? entity : new EntityFixture(); merkleTree = merkleTree != null ? merkleTree : MerkleTreeFixture.createMerkleTree(5); Assertions.assertEquals(merkleTree.size(), 5); // fixture sanity check. @@ -121,7 +121,7 @@ public static void testVerification(Entity entity, MerkleTree merkleTree) { * @param e1 the entity to put in the merkle tree * @param merkleTree the merkle tree to put the entity in */ - public static void testPutGetSameProof(Entity e1, MerkleTree merkleTree) { + public static void testPutGetSameProof(Entity e1, MerkleTreeInMemoryState merkleTree) { merkleTree = merkleTree != null ? merkleTree : MerkleTreeFixture.createMerkleTree(5); Assertions.assertEquals(merkleTree.size(), 5); // fixture sanity check. e1 = e1 != null ? e1 : new EntityFixture(); @@ -156,7 +156,7 @@ public static void testPutGetSameProof(Entity e1, MerkleTree merkleTree) { * @param entity the entity to put in the merkle tree * @param merkleTree the merkle tree to put the entity in */ - public static void testPutExistingEntity(Entity entity, MerkleTree merkleTree) { + public static void testPutExistingEntity(Entity entity, MerkleTreeInMemoryState merkleTree) { entity = entity != null ? entity : new EntityFixture(); merkleTree = merkleTree != null ? merkleTree : MerkleTreeFixture.createMerkleTree(5); Assertions.assertEquals(merkleTree.size(), 5); // fixture sanity check. @@ -182,7 +182,7 @@ public static void testPutExistingEntity(Entity entity, MerkleTree merkleTree) { * @param type the type of entity which is put in the merkle tree * @param merkleTree the merkle tree to put the entity in */ - public static void testConcurrentPutGet(String type, MerkleTree merkleTree) { + public static void testConcurrentPutGet(String type, MerkleTreeInMemoryState merkleTree) { int concurrencyDegree = 100; ArrayList entities = new ArrayList<>(); ArrayList ids = new ArrayList<>(); @@ -195,7 +195,7 @@ public static void testConcurrentPutGet(String type, MerkleTree merkleTree) { Thread[] getThreads = new Thread[concurrencyDegree]; merkleTree = merkleTree != null ? merkleTree : MerkleTreeFixture.createMerkleTree(0); - MerkleTree finalMerkleTree = merkleTree; + MerkleTreeInMemoryState finalMerkleTree = merkleTree; Assertions.assertEquals(merkleTree.size(), 0); // fixture sanity check. for (int i = 0; i < concurrencyDegree; i++) { @@ -267,12 +267,12 @@ public static void testConcurrentPutGet(String type, MerkleTree merkleTree) { * @param entity the entity to put in the merkle tree * @param merkleTree the merkle tree to put the entity in */ - public static void testGetNonExistingEntity(Entity entity, MerkleTree merkleTree) { + public static void testGetNonExistingEntity(Entity entity, MerkleTreeInMemoryState merkleTree) { merkleTree = merkleTree != null ? merkleTree : MerkleTreeFixture.createMerkleTree(5); Assertions.assertEquals(merkleTree.size(), 5); // fixture sanity check. entity = entity != null ? entity : new EntityFixture(); Entity finalEntity = entity; - MerkleTree finalMerkleTree = merkleTree; + MerkleTreeInMemoryState finalMerkleTree = merkleTree; Assertions.assertThrows(IllegalArgumentException.class, () -> finalMerkleTree.get(finalEntity.id())); } @@ -281,10 +281,10 @@ public static void testGetNonExistingEntity(Entity entity, MerkleTree merkleTree * * @param merkleTree the merkle tree to put null */ - public static void testNullInsertion(MerkleTree merkleTree) { + public static void testNullInsertion(MerkleTreeInMemoryState merkleTree) { merkleTree = merkleTree != null ? merkleTree : MerkleTreeFixture.createMerkleTree(5); Assertions.assertEquals(merkleTree.size(), 5); // fixture sanity check. - MerkleTree finalMerkleTree = merkleTree; + MerkleTreeInMemoryState finalMerkleTree = merkleTree; Assertions.assertThrows(IllegalArgumentException.class, () -> finalMerkleTree.put(null)); } @@ -294,7 +294,7 @@ public static void testNullInsertion(MerkleTree merkleTree) { * @param entity the entity to put in the merkle tree * @param merkleTree the merkle tree to put the entity in */ - public static void testManipulatedRoot(Entity entity, MerkleTree merkleTree) { + public static void testManipulatedRoot(Entity entity, MerkleTreeInMemoryState merkleTree) { entity = entity != null ? entity : new EntityFixture(); merkleTree = merkleTree != null ? merkleTree : MerkleTreeFixture.createMerkleTree(5); Assertions.assertEquals(merkleTree.size(), 5); // fixture sanity check. @@ -321,7 +321,7 @@ public static void testManipulatedRoot(Entity entity, MerkleTree merkleTree) { * @param entity the entity to put in the merkle tree * @param merkleTree the merkle tree to put the entity in */ - public static void testManipulatedEntity(Entity entity, MerkleTree merkleTree) { + public static void testManipulatedEntity(Entity entity, MerkleTreeInMemoryState merkleTree) { entity = entity != null ? entity : new EntityFixture(); merkleTree = merkleTree != null ? merkleTree : MerkleTreeFixture.createMerkleTree(5); Assertions.assertEquals(merkleTree.size(), 5); // fixture sanity check. @@ -343,7 +343,7 @@ public static void testManipulatedEntity(Entity entity, MerkleTree merkleTree) { * @param entity the entity to put in the merkle tree * @param merkleTree the merkle tree to put the entity in */ - public static void testManipulatedProof(Entity entity, MerkleTree merkleTree) { + public static void testManipulatedProof(Entity entity, MerkleTreeInMemoryState merkleTree) { entity = entity != null ? entity : new EntityFixture(); merkleTree = merkleTree != null ? merkleTree : MerkleTreeFixture.createMerkleTree(5); Assertions.assertEquals(merkleTree.size(), 5); // fixture sanity check. diff --git a/src/test/java/storage/IdentifiersTest.java b/src/test/java/storage/IdentifiersTest.java index 445e989a..07f44159 100644 --- a/src/test/java/storage/IdentifiersTest.java +++ b/src/test/java/storage/IdentifiersTest.java @@ -73,6 +73,8 @@ void sequentialAddTest() throws IOException { ArrayList all = db.all(); Assertions.assertEquals(all.size(), 10); for (Identifier identifier : all) { + System.out.println(identifier); + System.out.println(allIds); Assertions.assertTrue(allIds.contains(identifier)); } db.closeDb(); diff --git a/src/test/java/storage/MerkleTreeStatesTest.java b/src/test/java/storage/MerkleTreeStatesTest.java new file mode 100644 index 00000000..bead8b35 --- /dev/null +++ b/src/test/java/storage/MerkleTreeStatesTest.java @@ -0,0 +1,385 @@ +package storage; + +import model.lightchain.Identifier; +import modules.ads.merkletree.MerkleTreeState; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.testcontainers.shaded.org.apache.commons.io.FileUtils; +import storage.mapdb.IdentifierMapDb; +import storage.mapdb.MerkleTreeStateMapDb; +import unittest.fixtures.IdentifierFixture; +import unittest.fixtures.MerkleTreeStateFixture; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Encapsulates tests for merkle tree states database. + */ +public class MerkleTreeStatesTest { + private static final String TEMP_DIR = "tempdir"; + private static final String TEMP_FILE = "tempfile.db"; + private Path tempdir; + private ArrayList allStates; + private MerkleTreeStateMapDb db; + + /** + * Set the tests up. + */ + @BeforeEach + void setUp() throws IOException { + Path currentRelativePath = Paths.get(""); + tempdir = Files.createTempDirectory(currentRelativePath, TEMP_DIR); + db = new MerkleTreeStateMapDb(tempdir.toAbsolutePath() + "/" + TEMP_FILE); + allStates = MerkleTreeStateFixture.newStates(10); + } + + /** + * When adding 10 new merkle tree states sequentially, the Add method must return true for all of them. + */ + @Test + void sequentialAddTest() throws IOException { + for (MerkleTreeState state : allStates) { + Assertions.assertTrue(db.add(state)); + } + for (MerkleTreeState state : allStates) { + Assertions.assertTrue(db.has(state)); + } + + ArrayList all = db.all(); + Assertions.assertEquals(all.size(), 10); + for (MerkleTreeState state : all) { + Assertions.assertTrue(allStates.contains(state)); + } + db.closeDb(); + FileUtils.deleteDirectory(new File(tempdir.toString())); + } + + /** + * Concurrent version of sequentialAddTest. + */ + @Test + void concurrentAddTest() throws IOException { + int concurrencyDegree = 10; + + AtomicInteger threadError = new AtomicInteger(); + CountDownLatch addDone = new CountDownLatch(concurrencyDegree); + Thread[] addThreads = new Thread[concurrencyDegree]; + + /* + Adding all states concurrently. + */ + for (int i = 0; i < allStates.size(); i++) { + int finalI = i; + addThreads[i] = new Thread(() -> { + if (!db.add(allStates.get(finalI))) { + threadError.getAndIncrement(); + } + addDone.countDown(); + }); + } + + for (Thread t : addThreads) { + t.start(); + } + try { + boolean doneOneTime = addDone.await(60, TimeUnit.SECONDS); + Assertions.assertTrue(doneOneTime); + } catch (InterruptedException e) { + Assertions.fail(); + } + + /* + Checking correctness of insertion by Has. + */ + CountDownLatch hasDone = new CountDownLatch(concurrencyDegree); + Thread[] hasThreads = new Thread[concurrencyDegree]; + for (int i = 0; i < allStates.size(); i++) { + int finalI = i; + hasThreads[i] = new Thread(() -> { + if (!db.has(allStates.get(finalI))) { + threadError.getAndIncrement(); + } + hasDone.countDown(); + }); + } + + for (Thread t : hasThreads) { + t.start(); + } + try { + boolean doneOneTime = hasDone.await(60, TimeUnit.SECONDS); + Assertions.assertTrue(doneOneTime); + } catch (InterruptedException e) { + Assertions.fail(); + } + + /* + Retrieving all concurrently. + */ + CountDownLatch doneAll = new CountDownLatch(concurrencyDegree); + Thread[] allThreads = new Thread[concurrencyDegree]; + ArrayList all = db.all(); + + for (int i = 0; i < all.size(); i++) { + int finalI = i; + allThreads[i] = new Thread(() -> { + if (!allStates.contains(allStates.get(finalI))) { + threadError.getAndIncrement(); + } + doneAll.countDown(); + }); + } + + for (Thread t : allThreads) { + t.start(); + } + try { + boolean doneOneTime = doneAll.await(60, TimeUnit.SECONDS); + Assertions.assertTrue(doneOneTime); + } catch (InterruptedException e) { + Assertions.fail(); + } + + Assertions.assertEquals(0, threadError.get()); + db.closeDb(); + FileUtils.deleteDirectory(new File(tempdir.toString())); + } + + /** + * Add 10 new states, check that they are added correctly, i.e., while adding each state Add must return. + * true, Has returns true for each of them, and All returns list of all of them. Then Remove the first 5 states. + * While Removing each of them, the Remove should return true. Then query all 10 states using Has. + * Has should return false for the first 5 states that have been removed. But for the last 5 states it. + * should return true. Also, All should return only the last 5 states. + */ + @Test + void removeFirstFiveTest() throws IOException { + + for (MerkleTreeState state : allStates) { + Assertions.assertTrue(db.add(state)); + } + // removes the first five + for (int i = 0; i < 5; i++) { + Assertions.assertTrue(db.remove(allStates.get(i))); + } + for (int i = 0; i < 10; i++) { + if (i < 5) { + Assertions.assertFalse(db.has(allStates.get(i)) || db.all().contains(allStates.get(i))); + } else { + Assertions.assertTrue(db.has(allStates.get(i)) && db.all().contains(allStates.get(i))); + } + } + db.closeDb(); + FileUtils.deleteDirectory(new File(tempdir.toString())); + } + + /** + * Concurrent version of removeFirstFiveTest.Add 10 states, remove first five. + * Check the correct ones removed. + */ + @Test + void concurrentRemoveFirstFiveTest() throws IOException { + int concurrencyDegree = 10; + AtomicInteger threadError = new AtomicInteger(); + CountDownLatch doneAdd = new CountDownLatch(concurrencyDegree); + Thread[] addThreads = new Thread[concurrencyDegree]; + + /* + Adding all concurrently. + */ + for (int i = 0; i < allStates.size(); i++) { + int finalI = i; + addThreads[i] = new Thread(() -> { + if (!db.add(allStates.get(finalI))) { + threadError.getAndIncrement(); + } + doneAdd.countDown(); + }); + } + + for (Thread t : addThreads) { + t.start(); + } + try { + boolean doneOneTime = doneAdd.await(60, TimeUnit.SECONDS); + Assertions.assertTrue(doneOneTime); + } catch (InterruptedException e) { + Assertions.fail(); + } + + /* + Removing first 5 concurrently + */ + int removeTill = concurrencyDegree / 2; + CountDownLatch doneRemove = new CountDownLatch(removeTill); + Thread[] removeThreads = new Thread[removeTill]; + for (int i = 0; i < removeTill; i++) { + int finalI = i; + removeThreads[i] = new Thread(() -> { + if (!db.remove(allStates.get(finalI))) { + threadError.getAndIncrement(); + } + doneRemove.countDown(); + }); + } + + for (Thread t : removeThreads) { + t.start(); + } + try { + boolean doneOneTime = doneRemove.await(60, TimeUnit.SECONDS); + Assertions.assertTrue(doneOneTime); + } catch (InterruptedException e) { + Assertions.fail(); + } + + /* + Checking for Has concurrently. + */ + CountDownLatch doneHas = new CountDownLatch(concurrencyDegree); + Thread[] hasThreads = new Thread[concurrencyDegree]; + for (int i = 0; i < allStates.size(); i++) { + int finalI = i; + int finalI1 = i; + hasThreads[i] = new Thread(() -> { + if (allStates.indexOf(allStates.get(finalI)) < 5) { + if (db.has(allStates.get(finalI1))) { + threadError.getAndIncrement(); + } + } else { + if (!db.has(allStates.get(finalI))) { + threadError.getAndIncrement(); + } + } + doneHas.countDown(); + }); + } + + for (Thread t : hasThreads) { + t.start(); + } + try { + boolean doneOneTime = doneHas.await(60, TimeUnit.SECONDS); + Assertions.assertTrue(doneOneTime); + } catch (InterruptedException e) { + Assertions.fail(); + } + + Assertions.assertEquals(0, threadError.get()); + db.closeDb(); + FileUtils.deleteDirectory(new File(tempdir.toString())); + } + + /** + * Add 10 new states and check that all of them are added correctly, i.e., while adding each state. + * Add must return true. + * Has returns true for each of them, and All returns list of all of them. + * Then try Adding all of them again, and Add should return false for each of them. + */ + @Test + void duplicationTest() throws IOException { + for (MerkleTreeState state : allStates) { + Assertions.assertTrue(db.add(state)); + } + for (MerkleTreeState state : allStates) { + Assertions.assertTrue(db.has(state)); + } + for (MerkleTreeState state : db.all()) { + Assertions.assertTrue(allStates.contains(state)); + } + for (MerkleTreeState state : allStates) { + Assertions.assertFalse(db.add(state)); + } + db.closeDb(); + FileUtils.deleteDirectory(new File(tempdir.toString())); + } + + /** + * Concurrent version of duplicationTest. + */ + @Test void concurrentDuplicationTest() throws IOException { + int concurrencyDegree = 10; + + /* + * Adding all concurrently. + */ + AtomicInteger threadError = new AtomicInteger(); + CountDownLatch doneAdd = new CountDownLatch(concurrencyDegree); + Thread[] addThreads = new Thread[concurrencyDegree]; + for (int i = 0; i < allStates.size(); i++) { + int finalI = i; + addThreads[i] = new Thread(() -> { + if (!db.add(allStates.get(finalI))) { + threadError.getAndIncrement(); + } + doneAdd.countDown(); + }); + } + + for (Thread t : addThreads) { + t.start(); + } + try { + boolean doneOneTime = doneAdd.await(60, TimeUnit.SECONDS); + Assertions.assertTrue(doneOneTime); + } catch (InterruptedException e) { + Assertions.fail(); + } + + CountDownLatch doneHas = new CountDownLatch(concurrencyDegree); + Thread[] allThreads = new Thread[concurrencyDegree]; + for (int i = 0; i < allStates.size(); i++) { + int finalI = i; + allThreads[i] = new Thread(() -> { + if (!db.has(allStates.get(finalI))) { + threadError.getAndIncrement(); + } + doneHas.countDown(); + }); + } + for (Thread t : allThreads) { + t.start(); + } + try { + boolean doneOneTime = doneHas.await(60, TimeUnit.SECONDS); + Assertions.assertTrue(doneOneTime); + } catch (InterruptedException e) { + Assertions.fail(); + } + + CountDownLatch doneDubAdd = new CountDownLatch(concurrencyDegree); + Thread[] addThreadsDup = new Thread[concurrencyDegree]; + for (int i = 0; i < allStates.size(); i++) { + int finalI = i; + addThreadsDup[i] = new Thread(() -> { + if (db.add(allStates.get(finalI))) { + threadError.getAndIncrement(); + } + doneDubAdd.countDown(); + }); + } + + for (Thread t : addThreadsDup) { + t.start(); + } + try { + boolean doneOneTime = doneDubAdd.await(60, TimeUnit.SECONDS); + Assertions.assertTrue(doneOneTime); + } catch (InterruptedException e) { + Assertions.fail(); + } + + Assertions.assertEquals(0, threadError.get()); + db.closeDb(); + FileUtils.deleteDirectory(new File(tempdir.toString())); + } +} diff --git a/src/test/java/storage/MerkleTreesTest.java b/src/test/java/storage/MerkleTreesTest.java deleted file mode 100644 index 8bec875f..00000000 --- a/src/test/java/storage/MerkleTreesTest.java +++ /dev/null @@ -1,137 +0,0 @@ -package storage; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; - -import model.Entity; -import modules.ads.AuthenticatedEntity; -import modules.ads.MerkleTreeTest; -import modules.ads.merkletree.MerkleTree; -import modules.ads.merkletree.MerkleTreeAuthenticatedEntityVerifier; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.testcontainers.shaded.org.apache.commons.io.FileUtils; -import storage.mapdb.EntityMapDb; -import storage.mapdb.MerkleNodeMapDb; -import storage.mapdb.MerkleTreeMapDb; -import unittest.fixtures.EntityFixture; -import unittest.fixtures.MerkleTreeFixture; - -public class MerkleTreesTest { - private static final String TEMP_DIR_1 = "tempdir1"; - private static final String TEMP_FILE_1 = "tempfile1.db"; - private static final String TEMP_DIR_2 = "tempdir2"; - private static final String TEMP_FILE_2 = "tempfile2.db"; - private Path tempdirNode; - private Path tempdirEntity; - private ArrayList allTrees; - private MerkleNodeMapDb dbNode; - private EntityMapDb dbEntity; - private MerkleTreeMapDb dbMerkleTree; - - /** - * Sets up the tests. - * - * @throws IOException if the temp directory cannot be created - */ - @BeforeEach - void setUp() throws IOException { - Path currentRelativePath = Paths.get(""); - tempdirNode = Files.createTempDirectory(currentRelativePath, TEMP_DIR_1); - tempdirEntity = Files.createTempDirectory(currentRelativePath, TEMP_DIR_2); - dbNode = new MerkleNodeMapDb(tempdirEntity.toAbsolutePath() + "/" + TEMP_FILE_1); - dbEntity = new EntityMapDb(tempdirEntity.toAbsolutePath() + "/" + TEMP_FILE_2); - dbMerkleTree = new MerkleTreeMapDb(dbNode, dbEntity); - } - - @AfterEach - void tearDown() throws IOException { - dbNode.closeDb(); - dbEntity.closeDb(); - dbMerkleTree.closeDb(); - FileUtils.deleteDirectory(tempdirNode.toFile()); - FileUtils.deleteDirectory(tempdirEntity.toFile()); - } - @Test - public void testVerification() { - for (int i = 0; i < 5; i++) { - Entity entity = new EntityFixture(); - dbMerkleTree.put(entity); - } - MerkleTreeTest.testVerification(null, dbMerkleTree); - } - - @Test - public void testPutGetSameProof() { - for (int i = 0; i < 5; i++) { - Entity entity = new EntityFixture(); - dbMerkleTree.put(entity); - } - MerkleTreeTest.testPutGetSameProof(null, dbMerkleTree); - } - - @Test - public void testPutExistingEntity() { - for (int i = 0; i < 5; i++) { - Entity entity = new EntityFixture(); - dbMerkleTree.put(entity); - } - MerkleTreeTest.testPutExistingEntity(null, dbMerkleTree); - } - - @Test - public void testConcurrentPutGet() { - MerkleTreeTest.testConcurrentPutGet(null, dbMerkleTree); - } - - @Test - public void testGetNonExistingEntity() { - for (int i = 0; i < 5; i++) { - Entity entity = new EntityFixture(); - dbMerkleTree.put(entity); - } - MerkleTreeTest.testGetNonExistingEntity(null, dbMerkleTree); - } - - @Test - public void testNullInsertion() { - for (int i = 0; i < 5; i++) { - Entity entity = new EntityFixture(); - dbMerkleTree.put(entity); - } - MerkleTreeTest.testNullInsertion(dbMerkleTree); - } - - @Test - public void testManipulatedRoot() { - for (int i = 0; i < 5; i++) { - Entity entity = new EntityFixture(); - dbMerkleTree.put(entity); - } - MerkleTreeTest.testManipulatedRoot(null, dbMerkleTree); - } - - @Test - public void testManipulatedEntity() { - for (int i = 0; i < 5; i++) { - Entity entity = new EntityFixture(); - dbMerkleTree.put(entity); - } - MerkleTreeTest.testManipulatedEntity(null, dbMerkleTree); - } - - @Test - public void testManipulatedProof() { - for (int i = 0; i < 5; i++) { - Entity entity = new EntityFixture(); - dbMerkleTree.put(entity); - } - MerkleTreeTest.testManipulatedProof(null, dbMerkleTree); - } -} diff --git a/src/test/java/unittest/fixtures/MerkleTreeFixture.java b/src/test/java/unittest/fixtures/MerkleTreeFixture.java index dfa603bd..6a5ff844 100644 --- a/src/test/java/unittest/fixtures/MerkleTreeFixture.java +++ b/src/test/java/unittest/fixtures/MerkleTreeFixture.java @@ -2,8 +2,7 @@ import java.util.ArrayList; -import model.lightchain.Identifier; -import modules.ads.merkletree.MerkleTree; +import modules.ads.merkletree.MerkleTreeInMemoryState; /** * Creates a new randomly looking MerkleTree. @@ -15,8 +14,8 @@ public class MerkleTreeFixture { * @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(); + public static MerkleTreeInMemoryState createMerkleTree(int n) { + MerkleTreeInMemoryState merkleTree = new MerkleTreeInMemoryState(); for (int i = 0; i < n; i++) { merkleTree.put(new EntityFixture()); } @@ -30,8 +29,8 @@ public static MerkleTree createMerkleTree(int n) { * @param m number of elements in each tree * @return an array list of n merkle trees with m random elements. */ - public static ArrayList newMerkleTrees(int n, int m) { - ArrayList trees = new ArrayList<>(); + public static ArrayList newMerkleTrees(int n, int m) { + ArrayList trees = new ArrayList<>(); for (int i = 0; i < n; i++) { trees.add(createMerkleTree(m)); } diff --git a/src/test/java/unittest/fixtures/MerkleTreeStateFixture.java b/src/test/java/unittest/fixtures/MerkleTreeStateFixture.java new file mode 100644 index 00000000..4c701b28 --- /dev/null +++ b/src/test/java/unittest/fixtures/MerkleTreeStateFixture.java @@ -0,0 +1,25 @@ +package unittest.fixtures; + +import model.lightchain.Identifier; +import modules.ads.merkletree.MerkleTreeInMemoryState; +import modules.ads.merkletree.MerkleTreeState; + +import java.util.ArrayList; + +/** + * Encapsulates utilities for a merkle tree state. + */ +public class MerkleTreeStateFixture { + public static MerkleTreeState newState() { + MerkleTreeInMemoryState tree = MerkleTreeFixture.createMerkleTree(10); + return tree.getState(); + } + + public static ArrayList newStates(int n) { + ArrayList states = new ArrayList<>(); + for (int i = 0; i < n; i++) { + states.add(MerkleTreeFixture.createMerkleTree(10).getState()); + } + return states; + } +} From 81a2966cd0b725d32cd49f23da10923f18152017 Mon Sep 17 00:00:00 2001 From: Ozan Date: Tue, 26 Apr 2022 21:14:35 +0300 Subject: [PATCH 05/12] makes merkle tree more generic --- src/main/java/modules/ads/MerkleTree.java | 20 ------ .../modules/ads/merkletree/MerkleTree.java | 67 +++++++++++++++++++ .../merkletree/MerkleTreeInMemoryState.java | 54 ++------------- 3 files changed, 74 insertions(+), 67 deletions(-) delete mode 100644 src/main/java/modules/ads/MerkleTree.java create mode 100644 src/main/java/modules/ads/merkletree/MerkleTree.java diff --git a/src/main/java/modules/ads/MerkleTree.java b/src/main/java/modules/ads/MerkleTree.java deleted file mode 100644 index e4d9df0e..00000000 --- a/src/main/java/modules/ads/MerkleTree.java +++ /dev/null @@ -1,20 +0,0 @@ -package modules.ads; - -import model.Entity; -import model.lightchain.Identifier; -import modules.ads.merkletree.MerkleProof; - -public interface MerkleTree { - default modules.ads.AuthenticatedEntity put(Entity e) throws IllegalArgumentException{ - return null; - } - default modules.ads.AuthenticatedEntity get(Identifier id) throws IllegalArgumentException{ - return null; - } - default MerkleProof getProof(Identifier id) throws IllegalArgumentException{ - return null; - } - void buildMerkleTree(); - int size(); - byte[] getBytes(); -} 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..b7b24f43 --- /dev/null +++ b/src/main/java/modules/ads/merkletree/MerkleTree.java @@ -0,0 +1,67 @@ +package modules.ads.merkletree; + +import model.Entity; +import model.crypto.Sha3256Hash; +import model.lightchain.Identifier; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.util.ArrayList; + +public interface MerkleTree { + + /** + * Returns the membership proof of the given identifier. + * + * @param id the identifier whose membership proof is to be returned + * @param state the state of the merkle tree + * @param root the root of the merkle tree + * + * @return the membership proof of the given identifier + * @throws IllegalArgumentException if the given identifier is not in the merkle tree + */ + default MerkleProof getProof(Identifier id, MerkleTreeState state, MerkleNode root) throws IllegalArgumentException { + ArrayList isLeftNode = new ArrayList<>(); + Sha3256Hash hash = new Sha3256Hash(id.getBytes()); + int idx = state.getNodeIndex(hash); + if (idx == -1) { + throw new IllegalArgumentException("identifier not found"); + } + ArrayList path = new ArrayList<>(); + MerkleNode currentNode = state.getNode(idx); + while (currentNode != root) { + path.add(currentNode.getSibling().getHash()); + isLeftNode.add(currentNode.isLeft()); + currentNode = currentNode.getParent(); + } + return new MerkleProof(path, root.getHash(), isLeftNode); + } + + default MerkleTreeState put(Entity e, MerkleTreeState state, int size) { + Sha3256Hash hash = new Sha3256Hash(e.id().getBytes()); + int idx = state.getNodeIndex(hash); + if (idx == -1) { + state.addLeafNode(new MerkleNode(e, false)); + state.putLeafNodeHash(hash, size); + state.putEntityHashTable(e.id(), e); + } + return state; + } + + default byte[] getBytes() { + try { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ObjectOutputStream out = null; + out = new ObjectOutputStream(bos); + out.writeObject(this); + out.flush(); + byte[] bytes = bos.toByteArray(); + return bytes; + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + +} diff --git a/src/main/java/modules/ads/merkletree/MerkleTreeInMemoryState.java b/src/main/java/modules/ads/merkletree/MerkleTreeInMemoryState.java index 165889f7..608d26fa 100644 --- a/src/main/java/modules/ads/merkletree/MerkleTreeInMemoryState.java +++ b/src/main/java/modules/ads/merkletree/MerkleTreeInMemoryState.java @@ -19,10 +19,10 @@ * Implementation of an in-memory Authenticated Skip List * that is capable of storing and retrieval of LightChain entities. */ -public class MerkleTreeInMemoryState implements AuthenticatedDataStructure, Serializable { +public class MerkleTreeInMemoryState implements AuthenticatedDataStructure, Serializable, MerkleTree { private static final Sha3256Hasher hasher = new Sha3256Hasher(); private final ReentrantReadWriteLock lock; - private final MerkleTreeState state; + private MerkleTreeState state; private int size; private MerkleNode root; @@ -36,33 +36,25 @@ public MerkleTreeInMemoryState() { this.state = new MerkleTreeState(); } - @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()); - int idx = state.getNodeIndex(hash); + int idx = state.getNodeIndex(new Sha3256Hash(e.id().getBytes())); if (idx == -1) { - state.addLeafNode(new MerkleNode(e, false)); - state.putLeafNodeHash(hash, size); - state.putEntityHashTable(e.id(), e); + this.state = put(e, state, size); 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); } + MerkleProof proof = getProof(e.id(), state, root); + 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) { @@ -70,7 +62,7 @@ public modules.ads.AuthenticatedEntity get(Identifier id) throws IllegalArgument } try { lock.readLock().lock(); - proof = getProof(id); + proof = getProof(id, state, root); Entity e = state.getEntity(id); return new MerkleTreeAuthenticatedEntity(proof, e.type(), e); } finally { @@ -78,23 +70,6 @@ public modules.ads.AuthenticatedEntity get(Identifier id) throws IllegalArgument } } - private MerkleProof getProof(Identifier id) throws IllegalArgumentException { - ArrayList isLeftNode = new ArrayList<>(); - Sha3256Hash hash = new Sha3256Hash(id.getBytes()); - int idx = state.getNodeIndex(hash); - if (idx == -1) { - throw new IllegalArgumentException("identifier not found"); - } - ArrayList path = new ArrayList<>(); - MerkleNode currentNode = state.getNode(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 @@ -134,21 +109,6 @@ public int size() { return this.size; } - public byte[] getBytes() { - try { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - ObjectOutputStream out = null; - out = new ObjectOutputStream(bos); - out.writeObject(this); - out.flush(); - byte[] bytes = bos.toByteArray(); - return bytes; - } catch (IOException e) { - e.printStackTrace(); - } - return null; - } - public MerkleTreeState getState() { return state; } From 22c4f964f1787b10b902c323db090cc0fa74feef Mon Sep 17 00:00:00 2001 From: Ozan Date: Wed, 27 Apr 2022 15:33:30 +0300 Subject: [PATCH 06/12] implements on disk merkle tree --- .../ads/AuthenticatedDataStructure.java | 16 ++- .../modules/ads/merkletree/MerkleTree.java | 41 ++++++ .../merkletree/MerkleTreeInMemoryState.java | 31 +++- .../ads/merkletree/MerkleTreeOnDiskState.java | 136 ++++++++++++++++++ .../storage/mapdb/MerkleTreeStateMapDb.java | 26 ++++ .../ads/MerkleTreeInMemoryStateTest.java | 80 +++++++++++ .../ads/MerkleTreeOnDiskStateTest.java | 127 ++++++++++++++++ src/test/java/modules/ads/MerkleTreeTest.java | 122 +++------------- .../unittest/fixtures/MerkleTreeFixture.java | 5 +- .../fixtures/MerkleTreeStateFixture.java | 5 +- .../fixtures/MerkleTreeStateMapDbFixture.java | 16 +++ 11 files changed, 493 insertions(+), 112 deletions(-) create mode 100644 src/main/java/modules/ads/merkletree/MerkleTreeOnDiskState.java create mode 100644 src/test/java/modules/ads/MerkleTreeInMemoryStateTest.java create mode 100644 src/test/java/modules/ads/MerkleTreeOnDiskStateTest.java create mode 100644 src/test/java/unittest/fixtures/MerkleTreeStateMapDbFixture.java diff --git a/src/main/java/modules/ads/AuthenticatedDataStructure.java b/src/main/java/modules/ads/AuthenticatedDataStructure.java index 8b360017..19c640ac 100644 --- a/src/main/java/modules/ads/AuthenticatedDataStructure.java +++ b/src/main/java/modules/ads/AuthenticatedDataStructure.java @@ -7,9 +7,23 @@ * Models AuthenticatedDataStructure (ADS) and a key-value store of entities supported with membership proofs. */ public interface AuthenticatedDataStructure { + + /** + * Puts the given entity into the merkle tree and return AuthenticationEntity. + * + * @param e the entity to be put into the merkle tree + * + * @return AuthenticationEntity of the given entity + */ AuthenticatedEntity put(Entity e); + /** + * Return the AuthenticationEntity of the given identifier. + * + * @param id the identifier whose AuthenticationEntity is to be returned + * + * @return the AuthenticationEntity of the given identifier + */ AuthenticatedEntity get(Identifier id); - int size(); } diff --git a/src/main/java/modules/ads/merkletree/MerkleTree.java b/src/main/java/modules/ads/merkletree/MerkleTree.java index b7b24f43..2182b963 100644 --- a/src/main/java/modules/ads/merkletree/MerkleTree.java +++ b/src/main/java/modules/ads/merkletree/MerkleTree.java @@ -38,6 +38,15 @@ default MerkleProof getProof(Identifier id, MerkleTreeState state, MerkleNode ro return new MerkleProof(path, root.getHash(), isLeftNode); } + /** + * Puts the given entity into the state and returns the state. + * + * @param e the entity to be put into the state + * @param state the state to be updated + * @param size the size of the state + * + * @return the updated state + */ default MerkleTreeState put(Entity e, MerkleTreeState state, int size) { Sha3256Hash hash = new Sha3256Hash(e.id().getBytes()); int idx = state.getNodeIndex(hash); @@ -49,6 +58,31 @@ default MerkleTreeState put(Entity e, MerkleTreeState state, int size) { return state; } + /** + * Puts the given entity into the merkle tree and return AuthenticationEntity. + * + * @param e the entity to be put into the merkle tree + * + * @return AuthenticationEntity of the given entity + * @throws IllegalArgumentException if the entity is null + */ + modules.ads.AuthenticatedEntity put(Entity e) throws IllegalArgumentException; + + /** + * Return the AuthenticationEntity of the given identifier. + * + * @param id the identifier whose AuthenticationEntity is to be returned + * + * @return the AuthenticationEntity of the given identifier + * @throws IllegalArgumentException if the identifier is not found + */ + modules.ads.AuthenticatedEntity get(Identifier id) throws IllegalArgumentException; + + /** + * Returns the byte array of the merkle tree. + * + * @return the byte array of the merkle tree + */ default byte[] getBytes() { try { ByteArrayOutputStream bos = new ByteArrayOutputStream(); @@ -64,4 +98,11 @@ default byte[] getBytes() { return null; } + /** + * Returns the size of the ADS. + * + * @return the size of the ADS + */ + int size(); + } diff --git a/src/main/java/modules/ads/merkletree/MerkleTreeInMemoryState.java b/src/main/java/modules/ads/merkletree/MerkleTreeInMemoryState.java index 608d26fa..9b2ae35f 100644 --- a/src/main/java/modules/ads/merkletree/MerkleTreeInMemoryState.java +++ b/src/main/java/modules/ads/merkletree/MerkleTreeInMemoryState.java @@ -1,12 +1,7 @@ package modules.ads.merkletree; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; import java.util.concurrent.locks.ReentrantReadWriteLock; import crypto.Sha3256Hasher; @@ -27,7 +22,7 @@ public class MerkleTreeInMemoryState implements AuthenticatedDataStructure, Seri private MerkleNode root; /** - * Default constructor for a Merkle Tree. + * Default constructor for an in memory Merkle Tree. */ public MerkleTreeInMemoryState() { this.size = 0; @@ -36,6 +31,14 @@ public MerkleTreeInMemoryState() { this.state = new MerkleTreeState(); } + /** + * Puts the given entity into the merkle tree and return AuthenticationEntity. + * + * @param e the entity to be put into the merkle tree + * + * @return AuthenticationEntity of the given entity + * @throws IllegalArgumentException if the entity is null + */ public modules.ads.AuthenticatedEntity put(Entity e) throws IllegalArgumentException { try { lock.writeLock().lock(); @@ -55,6 +58,14 @@ public modules.ads.AuthenticatedEntity put(Entity e) throws IllegalArgumentExcep } } + /** + * Return the AuthenticationEntity of the given identifier. + * + * @param id the identifier whose AuthenticationEntity is to be returned + * + * @return the AuthenticationEntity of the given identifier + * @throws IllegalArgumentException if the identifier is not found + */ public modules.ads.AuthenticatedEntity get(Identifier id) throws IllegalArgumentException { MerkleProof proof; if (id == null) { @@ -70,6 +81,9 @@ public modules.ads.AuthenticatedEntity get(Identifier id) throws IllegalArgument } } + /** + * Updates the merkle tree after a new entity is added. + */ private void buildMerkleTree() { // keeps nodes of the current level of the merkle tree // will be updated bottom up @@ -105,6 +119,11 @@ private void buildMerkleTree() { root = currentLevelNodes.get(0); } + /** + * Returns the size of the ADS. + * + * @return the size of the ADS + */ public int size() { return this.size; } diff --git a/src/main/java/modules/ads/merkletree/MerkleTreeOnDiskState.java b/src/main/java/modules/ads/merkletree/MerkleTreeOnDiskState.java new file mode 100644 index 00000000..7e22eba1 --- /dev/null +++ b/src/main/java/modules/ads/merkletree/MerkleTreeOnDiskState.java @@ -0,0 +1,136 @@ +package modules.ads.merkletree; + +import crypto.Sha3256Hasher; +import model.Entity; +import model.crypto.Sha3256Hash; +import model.lightchain.Identifier; +import modules.ads.AuthenticatedDataStructure; +import storage.mapdb.MerkleTreeStateMapDb; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +public class MerkleTreeOnDiskState implements AuthenticatedDataStructure, MerkleTree, Serializable { + private static final Sha3256Hasher hasher = new Sha3256Hasher(); + private final ReentrantReadWriteLock lock; + private MerkleTreeStateMapDb stateMapDb; + private int size; + private MerkleNode root; + private MerkleTreeState state; + + /** + * Default constructor for an on disk Merkle Tree. + */ + public MerkleTreeOnDiskState(MerkleTreeStateMapDb stateMapDb) { + this.size = 0; + this.root = new MerkleNode(); + this.lock = new ReentrantReadWriteLock(); + this.stateMapDb = stateMapDb; + if (stateMapDb.isEmpty()) { + this.state = new MerkleTreeState(); + this.stateMapDb.add(this.state); + } else { + this.state = stateMapDb.getLatest(); + } + } + + /** + * Puts the given entity into the merkle tree and return AuthenticationEntity. + * + * @param e the entity to be put into the merkle tree + * + * @return AuthenticationEntity of the given entity + * @throws IllegalArgumentException if the entity is null + */ + public modules.ads.AuthenticatedEntity put(Entity e) throws IllegalArgumentException { + try { + lock.writeLock().lock(); + if (e == null) { + throw new IllegalArgumentException("entity cannot be null"); + } + int idx = state.getNodeIndex(new Sha3256Hash(e.id().getBytes())); + if (idx == -1) { + MerkleTreeState newState = put(e, state, size); + stateMapDb.changeTo(state, newState); + this.state = newState; + size++; + buildMerkleTree(); + } + MerkleProof proof = getProof(e.id(), state, root); + return new MerkleTreeAuthenticatedEntity(proof, e.type(), e); + } finally { + lock.writeLock().unlock(); + } + } + + /** + * Return the AuthenticationEntity of the given identifier. + * + * @param id the identifier whose AuthenticationEntity is to be returned + * + * @return the AuthenticationEntity of the given identifier + * @throws IllegalArgumentException if the identifier is not found + */ + 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, state, root); + Entity e = state.getEntity(id); + return new MerkleTreeAuthenticatedEntity(proof, e.type(), e); + } finally { + lock.readLock().unlock(); + } + } + + /** + * Updates the merkle tree after a new entity is added. + */ + 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<>(state.getLeafNodes()); + + // 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); + } + + /** + * Returns the size of the ADS. + * + * @return the size of the ADS + */ + public int size() { + return this.size; + } +} diff --git a/src/main/java/storage/mapdb/MerkleTreeStateMapDb.java b/src/main/java/storage/mapdb/MerkleTreeStateMapDb.java index ea37dede..56abe080 100644 --- a/src/main/java/storage/mapdb/MerkleTreeStateMapDb.java +++ b/src/main/java/storage/mapdb/MerkleTreeStateMapDb.java @@ -70,6 +70,10 @@ public boolean has(MerkleTreeState merkleTreeState) { return hasBoolean; } + public boolean changeTo(MerkleTreeState state1, MerkleTreeState state2) { + return this.remove(state1) && this.add(state2); + } + /** * Removes a merkle tree state from the storage. * @@ -112,4 +116,26 @@ public ArrayList all() { public void closeDb() { db.close(); } + + /** + * Returns true if the database is empty, false if not. + * + * @return true if the database is empty, false if not. + */ + public boolean isEmpty() { + return merkleTreeStateMap.isEmpty(); + } + + /** + * Returns the last element on the database. + * + * @return the last element on the database. + */ + public MerkleTreeState getLatest() { + ArrayList states = this.all(); + if (states.isEmpty()) { + return null; + } + return states.get(states.size() - 1); + } } diff --git a/src/test/java/modules/ads/MerkleTreeInMemoryStateTest.java b/src/test/java/modules/ads/MerkleTreeInMemoryStateTest.java new file mode 100644 index 00000000..88885bfe --- /dev/null +++ b/src/test/java/modules/ads/MerkleTreeInMemoryStateTest.java @@ -0,0 +1,80 @@ +package modules.ads; + +import org.junit.jupiter.api.Test; + +public class MerkleTreeInMemoryStateTest { + + /** + * Test putting and verifying a random entity in a random merkle tree. + */ + @Test + public void testVerificationNoArg() { + MerkleTreeTest.testVerification(null, null); + } + + /** + * Tests both putting and getting the same random entity gives same proof and putting + * another entity gives different proofs. + */ + @Test + public void testPutGetSameProofNoArg() { + MerkleTreeTest.testPutGetSameProof(null, null); + } + + /** + * Tests putting an existing entity does not change the proof with a random entity and merkle tree. + */ + @Test + public void testPutExistingEntityNoArg() { + MerkleTreeTest.testPutExistingEntity(null, null); + } + + /** + * Concurrently puts and gets entities and checks their proofs are correct (thread safety check). + */ + @Test + public void testConcurrentPutGetNoArg() { + MerkleTreeTest.testConcurrentPutGet(null, null); + } + + /** + * Tests getting an entity that does not exist in the merkle tree throws IllegalArgumentException + * with a random entity and merkle tree. + */ + @Test + public void testGetNonExistingEntityNoArg() { + MerkleTreeTest.testGetNonExistingEntity(null, null); + } + + /** + * Tests inserting null throws IllegalArgumentException with a random merkle tree. + */ + @Test + public void testNullInsertionNoArg() { + MerkleTreeTest.testNullInsertion(null); + } + + /** + * Tests the proof verification fails when root is changed with random entities and merkle tree. + */ + @Test + public void testManipulatedRootNoArg() { + MerkleTreeTest.testManipulatedRoot(null, null); + } + + /** + * Tests the proof verification fails when entity is changed with random entity and merkle tree. + */ + @Test + public void testManipulatedEntityNoArg() { + MerkleTreeTest.testManipulatedEntity(null, null); + } + + /** + * Tests the proof verification fails when entity is changed with random entity and merkle tree. + */ + @Test + public void testManipulatedProofNoArg() { + MerkleTreeTest.testManipulatedProof(null, null); + } +} diff --git a/src/test/java/modules/ads/MerkleTreeOnDiskStateTest.java b/src/test/java/modules/ads/MerkleTreeOnDiskStateTest.java new file mode 100644 index 00000000..6abcee61 --- /dev/null +++ b/src/test/java/modules/ads/MerkleTreeOnDiskStateTest.java @@ -0,0 +1,127 @@ +package modules.ads; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import modules.ads.merkletree.MerkleTree; +import modules.ads.merkletree.MerkleTreeOnDiskState; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.testcontainers.shaded.org.apache.commons.io.FileUtils; +import storage.mapdb.MerkleTreeStateMapDb; +import unittest.fixtures.EntityFixture; +import unittest.fixtures.MerkleTreeStateMapDbFixture; + +public class MerkleTreeOnDiskStateTest { + private static final String TEMP_DIR = "tempdir"; + private static Path tempdir; + MerkleTreeStateMapDb stateMapDb; + MerkleTree merkleTree; + + /** + * Set the tests up. + */ + @BeforeEach + void setUp() throws IOException { + Path currentRelativePath = Paths.get(""); + tempdir = Files.createTempDirectory(currentRelativePath, TEMP_DIR); + stateMapDb = MerkleTreeStateMapDbFixture.createMerkleTreeStateMapDb(tempdir); + merkleTree = new MerkleTreeOnDiskState(stateMapDb); + for (int i = 0; i < 5; i++) { + merkleTree.put(new EntityFixture()); + } + } + + /** + * Clean up after the tests. + */ + @AfterEach + void tearDown() throws IOException { + stateMapDb.closeDb(); + FileUtils.deleteDirectory(new File(tempdir.toString())); + } + + /** + * Test putting and verifying a random entity in a random an on disk merkle tree. + */ + @Test + public void testVerificationNoArg() { + MerkleTreeTest.testVerification(null, merkleTree); + } + + /** + * Tests both putting and getting the same random entity gives same proof and putting + * another entity gives different proofs. + */ + @Test + public void testPutGetSameProofNoArg() { + MerkleTreeTest.testPutGetSameProof(null, merkleTree); + } + + /** + * Tests putting an existing entity does not change the proof with a random entity and an on disk merkle tree. + */ + @Test + public void testPutExistingEntityNoArg() throws IOException { + MerkleTreeTest.testPutExistingEntity(null, merkleTree); + } + + /** + * Concurrently puts and gets entities and checks their proofs are correct (thread safety check). + */ + @Test + public void testConcurrentPutGetNoArg() throws IOException { + stateMapDb.closeDb(); + FileUtils.deleteDirectory(new File(tempdir.toString())); + Path currentRelativePath = Paths.get(""); + tempdir = Files.createTempDirectory(currentRelativePath, TEMP_DIR); + stateMapDb = MerkleTreeStateMapDbFixture.createMerkleTreeStateMapDb(tempdir); + merkleTree = new MerkleTreeOnDiskState(stateMapDb); + MerkleTreeTest.testConcurrentPutGet(null, merkleTree); + } + + /** + * Tests getting an entity that does not exist in the merkle tree throws IllegalArgumentException + * with a random entity and an on disk merkle tree. + */ + @Test + public void testGetNonExistingEntityNoArg() { + MerkleTreeTest.testGetNonExistingEntity(null, merkleTree); + } + + /** + * Tests inserting null throws IllegalArgumentException with a random an on disk merkle tree. + */ + @Test + public void testNullInsertionNoArg() { + MerkleTreeTest.testNullInsertion(merkleTree); + } + + /** + * Tests the proof verification fails when root is changed with random entities and an on disk merkle tree. + */ + @Test + public void testManipulatedRootNoArg() { + MerkleTreeTest.testManipulatedRoot(null, merkleTree); + } + + /** + * Tests the proof verification fails when entity is changed with random entity and an on disk merkle tree. + */ + @Test + public void testManipulatedEntityNoArg() { + MerkleTreeTest.testManipulatedEntity(null, merkleTree); + } + + /** + * Tests the proof verification fails when entity is changed with random entity and an on disk merkle tree. + */ + @Test + public void testManipulatedProofNoArg() { + MerkleTreeTest.testManipulatedProof(null, merkleTree); + } +} diff --git a/src/test/java/modules/ads/MerkleTreeTest.java b/src/test/java/modules/ads/MerkleTreeTest.java index 5a31f3f4..b6b9ce21 100644 --- a/src/test/java/modules/ads/MerkleTreeTest.java +++ b/src/test/java/modules/ads/MerkleTreeTest.java @@ -9,12 +9,8 @@ import model.codec.EntityType; import model.crypto.Sha3256Hash; import model.lightchain.Identifier; -import modules.ads.merkletree.MerkleProof; -import modules.ads.merkletree.MerkleTreeInMemoryState; -import modules.ads.merkletree.MerkleTreeAuthenticatedEntity; -import modules.ads.merkletree.MerkleTreeAuthenticatedEntityVerifier; +import modules.ads.merkletree.*; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; import unittest.fixtures.*; /** @@ -22,89 +18,15 @@ */ public class MerkleTreeTest { - /** - * Test putting and verifying a random entity in a random merkle tree. - */ - @Test - public void testVerificationNoArg() { - testVerification(null, null); - } - - /** - * Tests both putting and getting the same random entity gives same proof and putting - * another entity gives different proofs. - */ - @Test - public void testPutGetSameProofNoArg() { - testPutGetSameProof(null, null); - } - - /** - * Tests putting an existing entity does not change the proof with a random entity and merkle tree. - */ - @Test - public void testPutExistingEntityNoArg() { - testPutExistingEntity(null, null); - } - - /** - * Concurrently puts and gets entities and checks their proofs are correct (thread safety check). - */ - @Test - public void testConcurrentPutGetNoArg() { - testConcurrentPutGet(null, null); - } - - /** - * Tests getting an entity that does not exist in the merkle tree throws IllegalArgumentException - * with a random entity and merkle tree. - */ - @Test - public void testGetNonExistingEntityNoArg() { - testGetNonExistingEntity(null, null); - } - - /** - * Tests inserting null throws IllegalArgumentException with a random merkle tree. - */ - @Test - public void testNullInsertionNoArg() { - testNullInsertion(null); - } - - /** - * Tests the proof verification fails when root is changed with random entities and merkle tree. - */ - @Test - public void testManipulatedRootNoArg() { - testManipulatedRoot(null, null); - } - - /** - * Tests the proof verification fails when entity is changed with random entity and merkle tree. - */ - @Test - public void testManipulatedEntityNoArg() { - testManipulatedEntity(null, null); - } - - /** - * Tests the proof verification fails when entity is changed with random entity and merkle tree. - */ - @Test - public void testManipulatedProofNoArg() { - testManipulatedProof(null, null); - } - /** * Generic function to test putting and verifying an entity in a merkle tree. * * @param entity the entity to put in the merkle tree * @param merkleTree the merkle tree to put the entity in */ - public static void testVerification(Entity entity, MerkleTreeInMemoryState merkleTree) { + public static void testVerification(Entity entity, MerkleTree merkleTree) { entity = entity != null ? entity : new EntityFixture(); - merkleTree = merkleTree != null ? merkleTree : MerkleTreeFixture.createMerkleTree(5); + merkleTree = merkleTree != null ? merkleTree : MerkleTreeFixture.createInMemoryStateMerkleTree(5); Assertions.assertEquals(merkleTree.size(), 5); // fixture sanity check. merkleTree.put(entity); Assertions.assertEquals(merkleTree.size(), 6); @@ -121,8 +43,8 @@ public static void testVerification(Entity entity, MerkleTreeInMemoryState merkl * @param e1 the entity to put in the merkle tree * @param merkleTree the merkle tree to put the entity in */ - public static void testPutGetSameProof(Entity e1, MerkleTreeInMemoryState merkleTree) { - merkleTree = merkleTree != null ? merkleTree : MerkleTreeFixture.createMerkleTree(5); + public static void testPutGetSameProof(Entity e1, MerkleTree merkleTree) { + merkleTree = merkleTree != null ? merkleTree : MerkleTreeFixture.createInMemoryStateMerkleTree(5); Assertions.assertEquals(merkleTree.size(), 5); // fixture sanity check. e1 = e1 != null ? e1 : new EntityFixture(); @@ -156,9 +78,9 @@ public static void testPutGetSameProof(Entity e1, MerkleTreeInMemoryState merkle * @param entity the entity to put in the merkle tree * @param merkleTree the merkle tree to put the entity in */ - public static void testPutExistingEntity(Entity entity, MerkleTreeInMemoryState merkleTree) { + public static void testPutExistingEntity(Entity entity, MerkleTree merkleTree) { entity = entity != null ? entity : new EntityFixture(); - merkleTree = merkleTree != null ? merkleTree : MerkleTreeFixture.createMerkleTree(5); + merkleTree = merkleTree != null ? merkleTree : MerkleTreeFixture.createInMemoryStateMerkleTree(5); Assertions.assertEquals(merkleTree.size(), 5); // fixture sanity check. // first time put @@ -182,7 +104,7 @@ public static void testPutExistingEntity(Entity entity, MerkleTreeInMemoryState * @param type the type of entity which is put in the merkle tree * @param merkleTree the merkle tree to put the entity in */ - public static void testConcurrentPutGet(String type, MerkleTreeInMemoryState merkleTree) { + public static void testConcurrentPutGet(String type, MerkleTree merkleTree) { int concurrencyDegree = 100; ArrayList entities = new ArrayList<>(); ArrayList ids = new ArrayList<>(); @@ -194,8 +116,8 @@ public static void testConcurrentPutGet(String type, MerkleTreeInMemoryState mer Thread[] putThreads = new Thread[concurrencyDegree]; Thread[] getThreads = new Thread[concurrencyDegree]; - merkleTree = merkleTree != null ? merkleTree : MerkleTreeFixture.createMerkleTree(0); - MerkleTreeInMemoryState finalMerkleTree = merkleTree; + merkleTree = merkleTree != null ? merkleTree : MerkleTreeFixture.createInMemoryStateMerkleTree(0); + MerkleTree finalMerkleTree = merkleTree; Assertions.assertEquals(merkleTree.size(), 0); // fixture sanity check. for (int i = 0; i < concurrencyDegree; i++) { @@ -267,12 +189,12 @@ public static void testConcurrentPutGet(String type, MerkleTreeInMemoryState mer * @param entity the entity to put in the merkle tree * @param merkleTree the merkle tree to put the entity in */ - public static void testGetNonExistingEntity(Entity entity, MerkleTreeInMemoryState merkleTree) { - merkleTree = merkleTree != null ? merkleTree : MerkleTreeFixture.createMerkleTree(5); + public static void testGetNonExistingEntity(Entity entity, MerkleTree merkleTree) { + merkleTree = merkleTree != null ? merkleTree : MerkleTreeFixture.createInMemoryStateMerkleTree(5); Assertions.assertEquals(merkleTree.size(), 5); // fixture sanity check. entity = entity != null ? entity : new EntityFixture(); Entity finalEntity = entity; - MerkleTreeInMemoryState finalMerkleTree = merkleTree; + MerkleTree finalMerkleTree = merkleTree; Assertions.assertThrows(IllegalArgumentException.class, () -> finalMerkleTree.get(finalEntity.id())); } @@ -281,10 +203,10 @@ public static void testGetNonExistingEntity(Entity entity, MerkleTreeInMemorySta * * @param merkleTree the merkle tree to put null */ - public static void testNullInsertion(MerkleTreeInMemoryState merkleTree) { - merkleTree = merkleTree != null ? merkleTree : MerkleTreeFixture.createMerkleTree(5); + public static void testNullInsertion(MerkleTree merkleTree) { + merkleTree = merkleTree != null ? merkleTree : MerkleTreeFixture.createInMemoryStateMerkleTree(5); Assertions.assertEquals(merkleTree.size(), 5); // fixture sanity check. - MerkleTreeInMemoryState finalMerkleTree = merkleTree; + MerkleTree finalMerkleTree = merkleTree; Assertions.assertThrows(IllegalArgumentException.class, () -> finalMerkleTree.put(null)); } @@ -294,9 +216,9 @@ public static void testNullInsertion(MerkleTreeInMemoryState merkleTree) { * @param entity the entity to put in the merkle tree * @param merkleTree the merkle tree to put the entity in */ - public static void testManipulatedRoot(Entity entity, MerkleTreeInMemoryState merkleTree) { + public static void testManipulatedRoot(Entity entity, MerkleTree merkleTree) { entity = entity != null ? entity : new EntityFixture(); - merkleTree = merkleTree != null ? merkleTree : MerkleTreeFixture.createMerkleTree(5); + merkleTree = merkleTree != null ? merkleTree : MerkleTreeFixture.createInMemoryStateMerkleTree(5); Assertions.assertEquals(merkleTree.size(), 5); // fixture sanity check. AuthenticatedEntity authenticatedEntity = merkleTree.put(entity); MembershipProof proof = authenticatedEntity.getMembershipProof(); @@ -321,9 +243,9 @@ public static void testManipulatedRoot(Entity entity, MerkleTreeInMemoryState me * @param entity the entity to put in the merkle tree * @param merkleTree the merkle tree to put the entity in */ - public static void testManipulatedEntity(Entity entity, MerkleTreeInMemoryState merkleTree) { + public static void testManipulatedEntity(Entity entity, MerkleTree merkleTree) { entity = entity != null ? entity : new EntityFixture(); - merkleTree = merkleTree != null ? merkleTree : MerkleTreeFixture.createMerkleTree(5); + merkleTree = merkleTree != null ? merkleTree : MerkleTreeFixture.createInMemoryStateMerkleTree(5); Assertions.assertEquals(merkleTree.size(), 5); // fixture sanity check. AuthenticatedEntity authenticatedEntity = merkleTree.put(entity); @@ -343,9 +265,9 @@ public static void testManipulatedEntity(Entity entity, MerkleTreeInMemoryState * @param entity the entity to put in the merkle tree * @param merkleTree the merkle tree to put the entity in */ - public static void testManipulatedProof(Entity entity, MerkleTreeInMemoryState merkleTree) { + public static void testManipulatedProof(Entity entity, MerkleTree merkleTree) { entity = entity != null ? entity : new EntityFixture(); - merkleTree = merkleTree != null ? merkleTree : MerkleTreeFixture.createMerkleTree(5); + merkleTree = merkleTree != null ? merkleTree : MerkleTreeFixture.createInMemoryStateMerkleTree(5); Assertions.assertEquals(merkleTree.size(), 5); // fixture sanity check. AuthenticatedEntity authenticatedEntity = merkleTree.put(entity); MembershipProof proof = authenticatedEntity.getMembershipProof(); diff --git a/src/test/java/unittest/fixtures/MerkleTreeFixture.java b/src/test/java/unittest/fixtures/MerkleTreeFixture.java index 6a5ff844..3530151c 100644 --- a/src/test/java/unittest/fixtures/MerkleTreeFixture.java +++ b/src/test/java/unittest/fixtures/MerkleTreeFixture.java @@ -8,13 +8,14 @@ * Creates a new randomly looking MerkleTree. */ public class MerkleTreeFixture { + /** * Creates a new merkle tree with n random elements. * * @param n number of elements to create * @return a new merkle tree with n random elements. */ - public static MerkleTreeInMemoryState createMerkleTree(int n) { + public static MerkleTreeInMemoryState createInMemoryStateMerkleTree(int n) { MerkleTreeInMemoryState merkleTree = new MerkleTreeInMemoryState(); for (int i = 0; i < n; i++) { merkleTree.put(new EntityFixture()); @@ -32,7 +33,7 @@ public static MerkleTreeInMemoryState createMerkleTree(int n) { public static ArrayList newMerkleTrees(int n, int m) { ArrayList trees = new ArrayList<>(); for (int i = 0; i < n; i++) { - trees.add(createMerkleTree(m)); + trees.add(createInMemoryStateMerkleTree(m)); } return trees; } diff --git a/src/test/java/unittest/fixtures/MerkleTreeStateFixture.java b/src/test/java/unittest/fixtures/MerkleTreeStateFixture.java index 4c701b28..46d003c3 100644 --- a/src/test/java/unittest/fixtures/MerkleTreeStateFixture.java +++ b/src/test/java/unittest/fixtures/MerkleTreeStateFixture.java @@ -1,6 +1,5 @@ package unittest.fixtures; -import model.lightchain.Identifier; import modules.ads.merkletree.MerkleTreeInMemoryState; import modules.ads.merkletree.MerkleTreeState; @@ -11,14 +10,14 @@ */ public class MerkleTreeStateFixture { public static MerkleTreeState newState() { - MerkleTreeInMemoryState tree = MerkleTreeFixture.createMerkleTree(10); + MerkleTreeInMemoryState tree = MerkleTreeFixture.createInMemoryStateMerkleTree(10); return tree.getState(); } public static ArrayList newStates(int n) { ArrayList states = new ArrayList<>(); for (int i = 0; i < n; i++) { - states.add(MerkleTreeFixture.createMerkleTree(10).getState()); + states.add(MerkleTreeFixture.createInMemoryStateMerkleTree(10).getState()); } return states; } diff --git a/src/test/java/unittest/fixtures/MerkleTreeStateMapDbFixture.java b/src/test/java/unittest/fixtures/MerkleTreeStateMapDbFixture.java new file mode 100644 index 00000000..3da52312 --- /dev/null +++ b/src/test/java/unittest/fixtures/MerkleTreeStateMapDbFixture.java @@ -0,0 +1,16 @@ +package unittest.fixtures; + +import storage.mapdb.MerkleTreeStateMapDb; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +public class MerkleTreeStateMapDbFixture { + private static final String TEMP_FILE = "tempfile.db"; + + public static MerkleTreeStateMapDb createMerkleTreeStateMapDb(Path tempdir) throws IOException { + return new MerkleTreeStateMapDb(tempdir.toAbsolutePath() + "/" + TEMP_FILE); + } +} From e4fb5b4c783a8801529aadc0c7acc86bb2ecc12b Mon Sep 17 00:00:00 2001 From: Ozan Date: Wed, 27 Apr 2022 15:42:16 +0300 Subject: [PATCH 07/12] supresses lint errors --- .../modules/ads/merkletree/MerkleNode.java | 8 +++- .../modules/ads/merkletree/MerkleTree.java | 16 +++---- .../ads/merkletree/MerkleTreeOnDiskState.java | 8 ++-- .../ads/merkletree/MerkleTreeState.java | 13 +++--- .../storage/mapdb/MerkleTreeStateMapDb.java | 39 ++++++++-------- src/test/java/modules/ads/MerkleTreeTest.java | 45 ++++++++++--------- .../java/storage/MerkleTreeStatesTest.java | 22 +++++---- .../unittest/fixtures/MerkleTreeFixture.java | 2 + .../fixtures/MerkleTreeStateFixture.java | 4 +- .../fixtures/MerkleTreeStateMapDbFixture.java | 6 +-- 10 files changed, 85 insertions(+), 78 deletions(-) diff --git a/src/main/java/modules/ads/merkletree/MerkleNode.java b/src/main/java/modules/ads/merkletree/MerkleNode.java index 6962a104..dda3eb3e 100644 --- a/src/main/java/modules/ads/merkletree/MerkleNode.java +++ b/src/main/java/modules/ads/merkletree/MerkleNode.java @@ -122,8 +122,12 @@ public MerkleNode getSibling() { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } MerkleNode that = (MerkleNode) o; return hash.equals(that.hash); } diff --git a/src/main/java/modules/ads/merkletree/MerkleTree.java b/src/main/java/modules/ads/merkletree/MerkleTree.java index 2182b963..6b979d6d 100644 --- a/src/main/java/modules/ads/merkletree/MerkleTree.java +++ b/src/main/java/modules/ads/merkletree/MerkleTree.java @@ -1,22 +1,22 @@ package modules.ads.merkletree; -import model.Entity; -import model.crypto.Sha3256Hash; -import model.lightchain.Identifier; - import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; import java.util.ArrayList; +import model.Entity; +import model.crypto.Sha3256Hash; +import model.lightchain.Identifier; + public interface MerkleTree { /** * Returns the membership proof of the given identifier. * - * @param id the identifier whose membership proof is to be returned + * @param id the identifier whose membership proof is to be returned * @param state the state of the merkle tree - * @param root the root of the merkle tree + * @param root the root of the merkle tree * * @return the membership proof of the given identifier * @throws IllegalArgumentException if the given identifier is not in the merkle tree @@ -41,9 +41,9 @@ default MerkleProof getProof(Identifier id, MerkleTreeState state, MerkleNode ro /** * Puts the given entity into the state and returns the state. * - * @param e the entity to be put into the state + * @param e the entity to be put into the state * @param state the state to be updated - * @param size the size of the state + * @param size the size of the state * * @return the updated state */ diff --git a/src/main/java/modules/ads/merkletree/MerkleTreeOnDiskState.java b/src/main/java/modules/ads/merkletree/MerkleTreeOnDiskState.java index 7e22eba1..660d690b 100644 --- a/src/main/java/modules/ads/merkletree/MerkleTreeOnDiskState.java +++ b/src/main/java/modules/ads/merkletree/MerkleTreeOnDiskState.java @@ -1,5 +1,9 @@ package modules.ads.merkletree; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.concurrent.locks.ReentrantReadWriteLock; + import crypto.Sha3256Hasher; import model.Entity; import model.crypto.Sha3256Hash; @@ -7,10 +11,6 @@ import modules.ads.AuthenticatedDataStructure; import storage.mapdb.MerkleTreeStateMapDb; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.concurrent.locks.ReentrantReadWriteLock; - public class MerkleTreeOnDiskState implements AuthenticatedDataStructure, MerkleTree, Serializable { private static final Sha3256Hasher hasher = new Sha3256Hasher(); private final ReentrantReadWriteLock lock; diff --git a/src/main/java/modules/ads/merkletree/MerkleTreeState.java b/src/main/java/modules/ads/merkletree/MerkleTreeState.java index 6daf34c2..f7492bbf 100644 --- a/src/main/java/modules/ads/merkletree/MerkleTreeState.java +++ b/src/main/java/modules/ads/merkletree/MerkleTreeState.java @@ -1,14 +1,17 @@ package modules.ads.merkletree; -import model.Entity; -import model.crypto.Sha3256Hash; -import model.lightchain.Identifier; - import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; import java.io.Serializable; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +import model.Entity; +import model.crypto.Sha3256Hash; +import model.lightchain.Identifier; public class MerkleTreeState implements Serializable { private ArrayList leafNodes; diff --git a/src/main/java/storage/mapdb/MerkleTreeStateMapDb.java b/src/main/java/storage/mapdb/MerkleTreeStateMapDb.java index 56abe080..f3905429 100644 --- a/src/main/java/storage/mapdb/MerkleTreeStateMapDb.java +++ b/src/main/java/storage/mapdb/MerkleTreeStateMapDb.java @@ -1,6 +1,11 @@ package storage.mapdb; -import model.lightchain.Identifier; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.util.ArrayList; +import java.util.concurrent.locks.ReentrantReadWriteLock; + import modules.ads.merkletree.MerkleTreeState; import org.mapdb.DB; import org.mapdb.DBMaker; @@ -8,12 +13,6 @@ import org.mapdb.Serializer; import storage.MerkleTreeStates; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.util.ArrayList; -import java.util.concurrent.locks.ReentrantReadWriteLock; - public class MerkleTreeStateMapDb implements MerkleTreeStates { private final DB db; private final ReentrantReadWriteLock lock; @@ -93,20 +92,20 @@ public boolean remove(MerkleTreeState merkleTreeState) { @Override public ArrayList all() { ArrayList states; - states = new ArrayList<>(); - for (byte[] element : merkleTreeStateMap.keySet()) { - try { - ByteArrayInputStream bis = new ByteArrayInputStream(element); - ObjectInputStream inp = null; - inp = new ObjectInputStream(bis); - MerkleTreeState state = (MerkleTreeState) inp.readObject(); - states.add(state); - } catch (IOException e) { - e.printStackTrace(); - } catch (ClassNotFoundException e) { - throw new RuntimeException(e); - } + states = new ArrayList<>(); + for (byte[] element : merkleTreeStateMap.keySet()) { + try { + ByteArrayInputStream bis = new ByteArrayInputStream(element); + ObjectInputStream inp = null; + inp = new ObjectInputStream(bis); + MerkleTreeState state = (MerkleTreeState) inp.readObject(); + states.add(state); + } catch (IOException e) { + e.printStackTrace(); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); } + } return states; } diff --git a/src/test/java/modules/ads/MerkleTreeTest.java b/src/test/java/modules/ads/MerkleTreeTest.java index b6b9ce21..0a75037f 100644 --- a/src/test/java/modules/ads/MerkleTreeTest.java +++ b/src/test/java/modules/ads/MerkleTreeTest.java @@ -9,7 +9,10 @@ import model.codec.EntityType; import model.crypto.Sha3256Hash; import model.lightchain.Identifier; -import modules.ads.merkletree.*; +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 unittest.fixtures.*; @@ -21,7 +24,7 @@ public class MerkleTreeTest { /** * Generic function to test putting and verifying an entity in a merkle tree. * - * @param entity the entity to put in the merkle tree + * @param entity the entity to put in the merkle tree * @param merkleTree the merkle tree to put the entity in */ public static void testVerification(Entity entity, MerkleTree merkleTree) { @@ -40,7 +43,7 @@ public static void testVerification(Entity entity, MerkleTree merkleTree) { * Generic function to test both putting and getting the same entity gives same proof * and putting another entity gives different proofs. * - * @param e1 the entity to put in the merkle tree + * @param e1 the entity to put in the merkle tree * @param merkleTree the merkle tree to put the entity in */ public static void testPutGetSameProof(Entity e1, MerkleTree merkleTree) { @@ -75,7 +78,7 @@ public static void testPutGetSameProof(Entity e1, MerkleTree merkleTree) { /** * Generic function which tests putting an existing entity does not change the proof. * - * @param entity the entity to put in the merkle tree + * @param entity the entity to put in the merkle tree * @param merkleTree the merkle tree to put the entity in */ public static void testPutExistingEntity(Entity entity, MerkleTree merkleTree) { @@ -101,7 +104,7 @@ public static void testPutExistingEntity(Entity entity, MerkleTree merkleTree) { * Generic function which concurrently puts and gets entities and checks their proofs are correct * (thread safety check). * - * @param type the type of entity which is put in the merkle tree + * @param type the type of entity which is put in the merkle tree * @param merkleTree the merkle tree to put the entity in */ public static void testConcurrentPutGet(String type, MerkleTree merkleTree) { @@ -186,7 +189,7 @@ public static void testConcurrentPutGet(String type, MerkleTree merkleTree) { * Generic function which tests getting an entity that does not exist in the merkle tree * throws IllegalArgumentException. * - * @param entity the entity to put in the merkle tree + * @param entity the entity to put in the merkle tree * @param merkleTree the merkle tree to put the entity in */ public static void testGetNonExistingEntity(Entity entity, MerkleTree merkleTree) { @@ -213,7 +216,7 @@ public static void testNullInsertion(MerkleTree merkleTree) { /** * Generic function which tests the proof verification fails when root is changed. * - * @param entity the entity to put in the merkle tree + * @param entity the entity to put in the merkle tree * @param merkleTree the merkle tree to put the entity in */ public static void testManipulatedRoot(Entity entity, MerkleTree merkleTree) { @@ -226,9 +229,9 @@ public static void testManipulatedRoot(Entity entity, MerkleTree merkleTree) { // 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()); + tamperedProof, + authenticatedEntity.type(), + authenticatedEntity.getEntity()); MerkleTreeAuthenticatedEntityVerifier verifier = new MerkleTreeAuthenticatedEntityVerifier(); // authenticated entity must be verified. @@ -240,7 +243,7 @@ public static void testManipulatedRoot(Entity entity, MerkleTree merkleTree) { /** * Generic function which tests the proof verification fails when entity is changed. * - * @param entity the entity to put in the merkle tree + * @param entity the entity to put in the merkle tree * @param merkleTree the merkle tree to put the entity in */ public static void testManipulatedEntity(Entity entity, MerkleTree merkleTree) { @@ -250,9 +253,9 @@ public static void testManipulatedEntity(Entity entity, MerkleTree merkleTree) { AuthenticatedEntity authenticatedEntity = merkleTree.put(entity); AuthenticatedEntity tamperedEntity = new MerkleTreeAuthenticatedEntity( - (MerkleProof) authenticatedEntity.getMembershipProof(), - authenticatedEntity.type(), - new EntityFixture()); + (MerkleProof) authenticatedEntity.getMembershipProof(), + authenticatedEntity.type(), + new EntityFixture()); MerkleTreeAuthenticatedEntityVerifier verifier = new MerkleTreeAuthenticatedEntityVerifier(); Assertions.assertTrue(verifier.verify(authenticatedEntity)); // original authenticated entity passes verification. @@ -262,7 +265,7 @@ public static void testManipulatedEntity(Entity entity, MerkleTree merkleTree) { /** * Generic function which tests the proof fails verification when proof part of authenticated entity is changed. * - * @param entity the entity to put in the merkle tree + * @param entity the entity to put in the merkle tree * @param merkleTree the merkle tree to put the entity in */ public static void testManipulatedProof(Entity entity, MerkleTree merkleTree) { @@ -273,12 +276,12 @@ public static void testManipulatedProof(Entity entity, MerkleTree merkleTree) { MembershipProof proof = authenticatedEntity.getMembershipProof(); AuthenticatedEntity tamperedEntity = new MerkleTreeAuthenticatedEntity( - new MerkleProof(Sha3256HashFixture.newSha3256HashArrayList( - proof.getPath().size()), - proof.getRoot(), - proof.getIsLeftNode()), - authenticatedEntity.type(), - authenticatedEntity.getEntity()); + 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. diff --git a/src/test/java/storage/MerkleTreeStatesTest.java b/src/test/java/storage/MerkleTreeStatesTest.java index bead8b35..251ed11c 100644 --- a/src/test/java/storage/MerkleTreeStatesTest.java +++ b/src/test/java/storage/MerkleTreeStatesTest.java @@ -1,16 +1,5 @@ package storage; -import model.lightchain.Identifier; -import modules.ads.merkletree.MerkleTreeState; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.testcontainers.shaded.org.apache.commons.io.FileUtils; -import storage.mapdb.IdentifierMapDb; -import storage.mapdb.MerkleTreeStateMapDb; -import unittest.fixtures.IdentifierFixture; -import unittest.fixtures.MerkleTreeStateFixture; - import java.io.File; import java.io.IOException; import java.nio.file.Files; @@ -21,6 +10,14 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import modules.ads.merkletree.MerkleTreeState; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.testcontainers.shaded.org.apache.commons.io.FileUtils; +import storage.mapdb.MerkleTreeStateMapDb; +import unittest.fixtures.MerkleTreeStateFixture; + /** * Encapsulates tests for merkle tree states database. */ @@ -306,7 +303,8 @@ void duplicationTest() throws IOException { /** * Concurrent version of duplicationTest. */ - @Test void concurrentDuplicationTest() throws IOException { + @Test + void concurrentDuplicationTest() throws IOException { int concurrencyDegree = 10; /* diff --git a/src/test/java/unittest/fixtures/MerkleTreeFixture.java b/src/test/java/unittest/fixtures/MerkleTreeFixture.java index 3530151c..4acde220 100644 --- a/src/test/java/unittest/fixtures/MerkleTreeFixture.java +++ b/src/test/java/unittest/fixtures/MerkleTreeFixture.java @@ -13,6 +13,7 @@ public class MerkleTreeFixture { * Creates a new merkle tree with n random elements. * * @param n number of elements to create + * * @return a new merkle tree with n random elements. */ public static MerkleTreeInMemoryState createInMemoryStateMerkleTree(int n) { @@ -28,6 +29,7 @@ public static MerkleTreeInMemoryState createInMemoryStateMerkleTree(int n) { * * @param n number of trees to create * @param m number of elements in each tree + * * @return an array list of n merkle trees with m random elements. */ public static ArrayList newMerkleTrees(int n, int m) { diff --git a/src/test/java/unittest/fixtures/MerkleTreeStateFixture.java b/src/test/java/unittest/fixtures/MerkleTreeStateFixture.java index 46d003c3..f27bd89f 100644 --- a/src/test/java/unittest/fixtures/MerkleTreeStateFixture.java +++ b/src/test/java/unittest/fixtures/MerkleTreeStateFixture.java @@ -1,10 +1,10 @@ package unittest.fixtures; +import java.util.ArrayList; + import modules.ads.merkletree.MerkleTreeInMemoryState; import modules.ads.merkletree.MerkleTreeState; -import java.util.ArrayList; - /** * Encapsulates utilities for a merkle tree state. */ diff --git a/src/test/java/unittest/fixtures/MerkleTreeStateMapDbFixture.java b/src/test/java/unittest/fixtures/MerkleTreeStateMapDbFixture.java index 3da52312..d28be294 100644 --- a/src/test/java/unittest/fixtures/MerkleTreeStateMapDbFixture.java +++ b/src/test/java/unittest/fixtures/MerkleTreeStateMapDbFixture.java @@ -1,11 +1,9 @@ package unittest.fixtures; -import storage.mapdb.MerkleTreeStateMapDb; - import java.io.IOException; -import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; + +import storage.mapdb.MerkleTreeStateMapDb; public class MerkleTreeStateMapDbFixture { private static final String TEMP_FILE = "tempfile.db"; From 5242a277e1f04fc645ff7f997003057b066c00c6 Mon Sep 17 00:00:00 2001 From: Ozan Date: Wed, 27 Apr 2022 16:00:46 +0300 Subject: [PATCH 08/12] supresses lint errors --- .../modules/ads/merkletree/MerkleNode.java | 47 +++++++++++ .../modules/ads/merkletree/MerkleTree.java | 3 + .../ads/merkletree/MerkleTreeOnDiskState.java | 3 + .../ads/merkletree/MerkleTreeState.java | 79 +++++++++++++++++++ src/main/java/modules/codec/JsonEncoder.java | 5 +- src/main/java/storage/MerkleTreeStates.java | 8 +- .../storage/mapdb/MerkleTreeStateMapDb.java | 11 +++ .../ads/MerkleTreeInMemoryStateTest.java | 3 + .../ads/MerkleTreeOnDiskStateTest.java | 3 + .../fixtures/MerkleTreeStateFixture.java | 12 +++ .../fixtures/MerkleTreeStateMapDbFixture.java | 11 +++ 11 files changed, 179 insertions(+), 6 deletions(-) diff --git a/src/main/java/modules/ads/merkletree/MerkleNode.java b/src/main/java/modules/ads/merkletree/MerkleNode.java index dda3eb3e..dc8ebd92 100644 --- a/src/main/java/modules/ads/merkletree/MerkleNode.java +++ b/src/main/java/modules/ads/merkletree/MerkleNode.java @@ -75,34 +75,65 @@ public MerkleNode(Sha3256Hash hash, MerkleNode left, MerkleNode right) { this.hash = hash; } + /** + * Returns the left child of the node. + * @return the left child of the node + */ @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "internal representation is intentionally returned") public MerkleNode getLeft() { return left; } + /** + * Returns the right child of the node. + * @return the right child of the node + */ @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "internal representation is intentionally returned") public MerkleNode getRight() { return right; } + /** + * Returns the parent node of the node. + * @return the parent node of the node + */ @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "internal representation is intentionally returned") public MerkleNode getParent() { return parent; } + /** + * Sets the parent node of the node. + * @return the parent node of the node + */ @SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "parent is intentionally mutable externally") public void setParent(MerkleNode parent) { this.parent = parent; } + /** + * Returns the hash of the node. + * + * @return the hash of the node + */ public Sha3256Hash getHash() { return hash; } + /** + * Returns the isLeft boolean of the node. + * + * @return the isLeft boolean of the node + */ public boolean isLeft() { return isLeft; } + /** + * Sets the isLeft of the node. + * + * @param isLeft isLeft boolean of the node + */ public void setLeft(boolean isLeft) { this.isLeft = isLeft; } @@ -120,6 +151,12 @@ public MerkleNode getSibling() { } } + /** + * Returns if o is equal to this node. + * + * @param o object to compare + * @return true if o is equal to this node + */ @Override public boolean equals(Object o) { if (this == o) { @@ -132,11 +169,21 @@ public boolean equals(Object o) { return hash.equals(that.hash); } + /** + * Returns the hash code of the node. + * + * @return the hash code of the node + */ @Override public int hashCode() { return Objects.hash(left, right, parent, isLeft, hash); } + /** + * Returns the byte array representation of the node. + * + * @return the byte array representation of the node + */ public byte[] getBytes() { Gson gson = new Gson(); byte[] bytes = gson.toJson(this).getBytes(StandardCharsets.UTF_8); diff --git a/src/main/java/modules/ads/merkletree/MerkleTree.java b/src/main/java/modules/ads/merkletree/MerkleTree.java index 6b979d6d..038373e5 100644 --- a/src/main/java/modules/ads/merkletree/MerkleTree.java +++ b/src/main/java/modules/ads/merkletree/MerkleTree.java @@ -9,6 +9,9 @@ import model.crypto.Sha3256Hash; import model.lightchain.Identifier; +/** + * MerkleTree interface. + */ public interface MerkleTree { /** diff --git a/src/main/java/modules/ads/merkletree/MerkleTreeOnDiskState.java b/src/main/java/modules/ads/merkletree/MerkleTreeOnDiskState.java index 660d690b..06f24645 100644 --- a/src/main/java/modules/ads/merkletree/MerkleTreeOnDiskState.java +++ b/src/main/java/modules/ads/merkletree/MerkleTreeOnDiskState.java @@ -11,6 +11,9 @@ import modules.ads.AuthenticatedDataStructure; import storage.mapdb.MerkleTreeStateMapDb; +/** + * On disk MerkleTree class. + */ public class MerkleTreeOnDiskState implements AuthenticatedDataStructure, MerkleTree, Serializable { private static final Sha3256Hasher hasher = new Sha3256Hasher(); private final ReentrantReadWriteLock lock; diff --git a/src/main/java/modules/ads/merkletree/MerkleTreeState.java b/src/main/java/modules/ads/merkletree/MerkleTreeState.java index f7492bbf..4af947cb 100644 --- a/src/main/java/modules/ads/merkletree/MerkleTreeState.java +++ b/src/main/java/modules/ads/merkletree/MerkleTreeState.java @@ -13,47 +13,97 @@ import model.crypto.Sha3256Hash; import model.lightchain.Identifier; +/** + * This class represents the state of the Merkle Tree. + */ public class MerkleTreeState implements Serializable { private ArrayList leafNodes; private Map leafNodesHashTable; private Map entityHashTable; + /** + * Constructor. + */ public MerkleTreeState() { this.leafNodes = new ArrayList<>(); this.leafNodesHashTable = new HashMap<>(); this.entityHashTable = new HashMap<>(); } + /** + * Constructor. + * + * @param leafNodes the leaf nodes of the Merkle Tree + * @param leafNodesHashTable the hash table of the leaf nodes + * @param entityHashTable the hash table of the entities + */ public MerkleTreeState(ArrayList leafNodes, Map leafNodesHashTable, Map entityHashTable) { this.leafNodes = leafNodes; this.leafNodesHashTable = leafNodesHashTable; this.entityHashTable = entityHashTable; } + /** + * Returns the leaf nodes of the Merkle Tree. + * + * @return the leaf nodes of the Merkle Tree + */ public ArrayList getLeafNodes() { return leafNodes; } + /** + * Returns the hash table of the leaf nodes. + * + * @return the hash table of the leaf nodes + */ public Map getLeafNodesHashTable() { return leafNodesHashTable; } + /** + * Returns the hash table of the entities. + * + * @return the hash table of the entities + */ public Map getEntityHashTable() { return entityHashTable; } + /** + * Adds a leaf node to the Merkle Tree. + * + * @param node the leaf node to be added + */ public void addLeafNode(MerkleNode node) { this.leafNodes.add(node); } + /** + * Adds a leaf node to the hash table of the leaf nodes. + * + * @param hash the hash of the leaf node + * @param idx the index of the leaf node + */ public void putLeafNodeHash(Sha3256Hash hash, Integer idx) { this.leafNodesHashTable.put(hash, idx); } + /** + * Adds an entity to the hash table of the entities. + * + * @param id the identifier of the entity + * @param e the entity + */ public void putEntityHashTable(Identifier id, Entity e) { this.entityHashTable.put(id, e); } + /** + * Returns the index of the node. + * + * @return the index of the node + */ public int getNodeIndex(Sha3256Hash hash) { if (this.leafNodesHashTable.get(hash) != null) { return this.leafNodesHashTable.get(hash); @@ -61,6 +111,11 @@ public int getNodeIndex(Sha3256Hash hash) { return -1; } + /** + * Returns the entity from hash table with a given id. + * + * @return the entity + */ public Entity getEntity(Identifier id) { if (this.entityHashTable.get(id) != null) { return this.entityHashTable.get(id); @@ -68,10 +123,24 @@ public Entity getEntity(Identifier id) { return null; } + /** + * Returns the node from its index. + * + * @param idx the index of the node + * + * @return the node + */ public MerkleNode getNode(int idx) { return this.leafNodes.get(idx); } + /** + * Returns if o is equal to this. + * + * @param o the object to be compared + * + * @return true if o is equal to this, false otherwise + */ @Override public boolean equals(Object o) { if (this == o) return true; @@ -80,11 +149,21 @@ public boolean equals(Object o) { return leafNodes.equals(that.leafNodes); } + /** + * Returns the hash code of this. + * + * @return the hash code of this + */ @Override public int hashCode() { return Objects.hash(leafNodes); } + /** + * Returns a byte array representation of this. + * + * @return a byte array representation of this + */ public byte[] getBytes() { try { ByteArrayOutputStream bos = new ByteArrayOutputStream(); diff --git a/src/main/java/modules/codec/JsonEncoder.java b/src/main/java/modules/codec/JsonEncoder.java index b7399f0e..5e075d6e 100644 --- a/src/main/java/modules/codec/JsonEncoder.java +++ b/src/main/java/modules/codec/JsonEncoder.java @@ -1,10 +1,7 @@ package modules.codec; import java.io.*; -import java.lang.reflect.Type; -import java.nio.charset.StandardCharsets; -import com.google.gson.Gson; import model.Entity; import model.codec.EncodedEntity; @@ -16,6 +13,7 @@ public class JsonEncoder implements Codec, Serializable { * Encodes an Entity to an EncodedEntity. * * @param e input Entity. + * * @return the JSON encoded representation of Entity. */ @Override @@ -39,6 +37,7 @@ public EncodedEntity encode(Entity e) { * Decodes a JSON EncodedEntity to its original Entity type. * * @param e input JSON EncodedEntity. + * * @return original Entity type. */ @Override diff --git a/src/main/java/storage/MerkleTreeStates.java b/src/main/java/storage/MerkleTreeStates.java index 1da10a50..3d046741 100644 --- a/src/main/java/storage/MerkleTreeStates.java +++ b/src/main/java/storage/MerkleTreeStates.java @@ -1,16 +1,16 @@ package storage; -import model.lightchain.Identifier; -import modules.ads.merkletree.MerkleTreeState; - import java.util.ArrayList; +import modules.ads.merkletree.MerkleTreeState; + public interface MerkleTreeStates { /** * Adds a merkle tree state to the storage, returns true if it is new, false if it already exists. * * @param merkleTreeState merkle tree state to be added to storage. + * * @return true if it is new, false if it already exists. */ boolean add(MerkleTreeState merkleTreeState); @@ -19,6 +19,7 @@ public interface MerkleTreeStates { * Checks existence of a merkle tree state on the storage. * * @param merkleTreeState merkle tree state to be checked. + * * @return true if it exists on the storage, false otherwise. */ boolean has(MerkleTreeState merkleTreeState); @@ -27,6 +28,7 @@ public interface MerkleTreeStates { * Removes a merkle tree state from the storage. * * @param merkleTreeState merkle tree state to be removed. + * * @return true if it exists and removed, false otherwise. */ boolean remove(MerkleTreeState merkleTreeState); diff --git a/src/main/java/storage/mapdb/MerkleTreeStateMapDb.java b/src/main/java/storage/mapdb/MerkleTreeStateMapDb.java index f3905429..a19ae46d 100644 --- a/src/main/java/storage/mapdb/MerkleTreeStateMapDb.java +++ b/src/main/java/storage/mapdb/MerkleTreeStateMapDb.java @@ -37,6 +37,7 @@ public MerkleTreeStateMapDb(String filePath) { * Adds a merkle tree state to the storage, returns true if it is new, false if it already exists. * * @param merkleTreeState merkle tree state to be added to storage. + * * @return true if it is new, false if it already exists. */ @Override @@ -55,6 +56,7 @@ public boolean add(MerkleTreeState merkleTreeState) { * Checks existence of a merkle tree state on the storage. * * @param merkleTreeState merkle tree state to be checked. + * * @return true if it exists on the storage, false otherwise. */ @Override @@ -69,6 +71,14 @@ public boolean has(MerkleTreeState merkleTreeState) { return hasBoolean; } + /** + * Removes the state1 and adds the state2. + * + * @param state1 the state to be removed. + * @param state2 the state to be added. + * + * @return + */ public boolean changeTo(MerkleTreeState state1, MerkleTreeState state2) { return this.remove(state1) && this.add(state2); } @@ -77,6 +87,7 @@ public boolean changeTo(MerkleTreeState state1, MerkleTreeState state2) { * Removes a merkle tree state from the storage. * * @param merkleTreeState merkle tree state to be removed. + * * @return true if it exists and removed, false otherwise. */ @Override diff --git a/src/test/java/modules/ads/MerkleTreeInMemoryStateTest.java b/src/test/java/modules/ads/MerkleTreeInMemoryStateTest.java index 88885bfe..e38d3da2 100644 --- a/src/test/java/modules/ads/MerkleTreeInMemoryStateTest.java +++ b/src/test/java/modules/ads/MerkleTreeInMemoryStateTest.java @@ -2,6 +2,9 @@ import org.junit.jupiter.api.Test; +/** + * Tests for MerkleTreeInMemoryState. + */ public class MerkleTreeInMemoryStateTest { /** diff --git a/src/test/java/modules/ads/MerkleTreeOnDiskStateTest.java b/src/test/java/modules/ads/MerkleTreeOnDiskStateTest.java index 6abcee61..99a654cd 100644 --- a/src/test/java/modules/ads/MerkleTreeOnDiskStateTest.java +++ b/src/test/java/modules/ads/MerkleTreeOnDiskStateTest.java @@ -16,6 +16,9 @@ import unittest.fixtures.EntityFixture; import unittest.fixtures.MerkleTreeStateMapDbFixture; +/** + * Tests for MerkleTreeOnDiskState. + */ public class MerkleTreeOnDiskStateTest { private static final String TEMP_DIR = "tempdir"; private static Path tempdir; diff --git a/src/test/java/unittest/fixtures/MerkleTreeStateFixture.java b/src/test/java/unittest/fixtures/MerkleTreeStateFixture.java index f27bd89f..d7e1790e 100644 --- a/src/test/java/unittest/fixtures/MerkleTreeStateFixture.java +++ b/src/test/java/unittest/fixtures/MerkleTreeStateFixture.java @@ -9,11 +9,23 @@ * Encapsulates utilities for a merkle tree state. */ public class MerkleTreeStateFixture { + /** + * Creates a new MerkleTreeInMemoryState with the 10 leaves. + * + * @return a new MerkleTreeInMemoryState with the 10 leaves. + */ public static MerkleTreeState newState() { MerkleTreeInMemoryState tree = MerkleTreeFixture.createInMemoryStateMerkleTree(10); return tree.getState(); } + /** + * Creates n MerkleTreeInMemoryState with the 10 leaves. + * + * @param n the number of states to create. + * + * @return an array of n MerkleTreeInMemoryState with the 10 leaves. + */ public static ArrayList newStates(int n) { ArrayList states = new ArrayList<>(); for (int i = 0; i < n; i++) { diff --git a/src/test/java/unittest/fixtures/MerkleTreeStateMapDbFixture.java b/src/test/java/unittest/fixtures/MerkleTreeStateMapDbFixture.java index d28be294..a4c9097f 100644 --- a/src/test/java/unittest/fixtures/MerkleTreeStateMapDbFixture.java +++ b/src/test/java/unittest/fixtures/MerkleTreeStateMapDbFixture.java @@ -5,9 +5,20 @@ import storage.mapdb.MerkleTreeStateMapDb; +/** + * Encapsulates utilities for the MerkleTreeStateMapDb. + */ public class MerkleTreeStateMapDbFixture { private static final String TEMP_FILE = "tempfile.db"; + /** + * Creates a new MerkleTreeStateMapDb instance. + * + * @param tempdir the temporary directory to use + * + * @return the new instance + * @throws IOException if the path is not found + */ public static MerkleTreeStateMapDb createMerkleTreeStateMapDb(Path tempdir) throws IOException { return new MerkleTreeStateMapDb(tempdir.toAbsolutePath() + "/" + TEMP_FILE); } From 2eca0f7ea551a68b2e339fbcc35275d58b6d20d9 Mon Sep 17 00:00:00 2001 From: Ozan Date: Wed, 27 Apr 2022 16:07:36 +0300 Subject: [PATCH 09/12] supresses lint errors --- .../modules/ads/merkletree/MerkleNode.java | 5 ++++- .../ads/merkletree/MerkleTreeState.java | 21 ++++++------------- src/main/java/storage/MerkleTreeStates.java | 3 +++ .../storage/mapdb/MerkleTreeStateMapDb.java | 5 ++++- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/main/java/modules/ads/merkletree/MerkleNode.java b/src/main/java/modules/ads/merkletree/MerkleNode.java index dc8ebd92..75148985 100644 --- a/src/main/java/modules/ads/merkletree/MerkleNode.java +++ b/src/main/java/modules/ads/merkletree/MerkleNode.java @@ -77,6 +77,7 @@ public MerkleNode(Sha3256Hash hash, MerkleNode left, MerkleNode right) { /** * Returns the left child of the node. + * * @return the left child of the node */ @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "internal representation is intentionally returned") @@ -86,6 +87,7 @@ public MerkleNode getLeft() { /** * Returns the right child of the node. + * * @return the right child of the node */ @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "internal representation is intentionally returned") @@ -95,6 +97,7 @@ public MerkleNode getRight() { /** * Returns the parent node of the node. + * * @return the parent node of the node */ @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "internal representation is intentionally returned") @@ -104,7 +107,6 @@ public MerkleNode getParent() { /** * Sets the parent node of the node. - * @return the parent node of the node */ @SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "parent is intentionally mutable externally") public void setParent(MerkleNode parent) { @@ -155,6 +157,7 @@ public MerkleNode getSibling() { * Returns if o is equal to this node. * * @param o object to compare + * * @return true if o is equal to this node */ @Override diff --git a/src/main/java/modules/ads/merkletree/MerkleTreeState.java b/src/main/java/modules/ads/merkletree/MerkleTreeState.java index 4af947cb..6c5c718e 100644 --- a/src/main/java/modules/ads/merkletree/MerkleTreeState.java +++ b/src/main/java/modules/ads/merkletree/MerkleTreeState.java @@ -30,19 +30,6 @@ public MerkleTreeState() { this.entityHashTable = new HashMap<>(); } - /** - * Constructor. - * - * @param leafNodes the leaf nodes of the Merkle Tree - * @param leafNodesHashTable the hash table of the leaf nodes - * @param entityHashTable the hash table of the entities - */ - public MerkleTreeState(ArrayList leafNodes, Map leafNodesHashTable, Map entityHashTable) { - this.leafNodes = leafNodes; - this.leafNodesHashTable = leafNodesHashTable; - this.entityHashTable = entityHashTable; - } - /** * Returns the leaf nodes of the Merkle Tree. * @@ -143,8 +130,12 @@ public MerkleNode getNode(int idx) { */ @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } MerkleTreeState that = (MerkleTreeState) o; return leafNodes.equals(that.leafNodes); } diff --git a/src/main/java/storage/MerkleTreeStates.java b/src/main/java/storage/MerkleTreeStates.java index 3d046741..87d699d4 100644 --- a/src/main/java/storage/MerkleTreeStates.java +++ b/src/main/java/storage/MerkleTreeStates.java @@ -4,6 +4,9 @@ import modules.ads.merkletree.MerkleTreeState; +/** + * Interface for the Merkle Tree States. + */ public interface MerkleTreeStates { /** diff --git a/src/main/java/storage/mapdb/MerkleTreeStateMapDb.java b/src/main/java/storage/mapdb/MerkleTreeStateMapDb.java index a19ae46d..82b35a3f 100644 --- a/src/main/java/storage/mapdb/MerkleTreeStateMapDb.java +++ b/src/main/java/storage/mapdb/MerkleTreeStateMapDb.java @@ -13,6 +13,9 @@ import org.mapdb.Serializer; import storage.MerkleTreeStates; +/** + * StateMapDb implementation for on-disk MerkleTreeState storage. + */ public class MerkleTreeStateMapDb implements MerkleTreeStates { private final DB db; private final ReentrantReadWriteLock lock; @@ -77,7 +80,7 @@ public boolean has(MerkleTreeState merkleTreeState) { * @param state1 the state to be removed. * @param state2 the state to be added. * - * @return + * @return true if the state1 exists and removed and state2 is added, false otherwise. */ public boolean changeTo(MerkleTreeState state1, MerkleTreeState state2) { return this.remove(state1) && this.add(state2); From 43e78d0c2c0d6f6aa7abe41a02dea73d8eb00416 Mon Sep 17 00:00:00 2001 From: Ozan Date: Wed, 27 Apr 2022 16:19:48 +0300 Subject: [PATCH 10/12] supresses spotbugs errors --- src/main/java/model/Entity.java | 4 +++- .../ads/merkletree/MerkleTreeInMemoryState.java | 2 +- .../ads/merkletree/MerkleTreeOnDiskState.java | 2 ++ .../modules/ads/merkletree/MerkleTreeState.java | 16 +++++++++++++--- .../java/storage/mapdb/MerkleTreeStateMapDb.java | 3 ++- 5 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/main/java/model/Entity.java b/src/main/java/model/Entity.java index e5be05ea..3e37bb32 100644 --- a/src/main/java/model/Entity.java +++ b/src/main/java/model/Entity.java @@ -1,5 +1,7 @@ package model; +import java.io.Serializable; + import crypto.Sha3256Hasher; import model.codec.EncodedEntity; import model.crypto.Hash; @@ -10,7 +12,7 @@ * Entity represents the unit of data model in LightChain. Everything meant to be sent over the network, stored * in memory, or a database must extend the Entity class. */ -public abstract class Entity { +public abstract class Entity implements Serializable { /** * Computes the collision resistant hash value of entity. * diff --git a/src/main/java/modules/ads/merkletree/MerkleTreeInMemoryState.java b/src/main/java/modules/ads/merkletree/MerkleTreeInMemoryState.java index 9b2ae35f..812eacb1 100644 --- a/src/main/java/modules/ads/merkletree/MerkleTreeInMemoryState.java +++ b/src/main/java/modules/ads/merkletree/MerkleTreeInMemoryState.java @@ -129,6 +129,6 @@ public int size() { } public MerkleTreeState getState() { - return state; + return new MerkleTreeState(this.state); } } diff --git a/src/main/java/modules/ads/merkletree/MerkleTreeOnDiskState.java b/src/main/java/modules/ads/merkletree/MerkleTreeOnDiskState.java index 06f24645..5ad70989 100644 --- a/src/main/java/modules/ads/merkletree/MerkleTreeOnDiskState.java +++ b/src/main/java/modules/ads/merkletree/MerkleTreeOnDiskState.java @@ -5,6 +5,7 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; import crypto.Sha3256Hasher; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import model.Entity; import model.crypto.Sha3256Hash; import model.lightchain.Identifier; @@ -25,6 +26,7 @@ public class MerkleTreeOnDiskState implements AuthenticatedDataStructure, Merkle /** * Default constructor for an on disk Merkle Tree. */ + @SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "state is intentionally mutable externally") public MerkleTreeOnDiskState(MerkleTreeStateMapDb stateMapDb) { this.size = 0; this.root = new MerkleNode(); diff --git a/src/main/java/modules/ads/merkletree/MerkleTreeState.java b/src/main/java/modules/ads/merkletree/MerkleTreeState.java index 6c5c718e..d51ad4b7 100644 --- a/src/main/java/modules/ads/merkletree/MerkleTreeState.java +++ b/src/main/java/modules/ads/merkletree/MerkleTreeState.java @@ -12,6 +12,7 @@ import model.Entity; import model.crypto.Sha3256Hash; import model.lightchain.Identifier; +import storage.MerkleTreeStates; /** * This class represents the state of the Merkle Tree. @@ -30,13 +31,22 @@ public MerkleTreeState() { this.entityHashTable = new HashMap<>(); } + /** + * Constructor. + */ + public MerkleTreeState(MerkleTreeState state) { + this.leafNodes = state.getLeafNodes(); + this.leafNodesHashTable = state.getLeafNodesHashTable(); + this.entityHashTable = state.getEntityHashTable(); + } + /** * Returns the leaf nodes of the Merkle Tree. * * @return the leaf nodes of the Merkle Tree */ public ArrayList getLeafNodes() { - return leafNodes; + return new ArrayList<>(leafNodes); } /** @@ -45,7 +55,7 @@ public ArrayList getLeafNodes() { * @return the hash table of the leaf nodes */ public Map getLeafNodesHashTable() { - return leafNodesHashTable; + return new HashMap<>(leafNodesHashTable); } /** @@ -54,7 +64,7 @@ public Map getLeafNodesHashTable() { * @return the hash table of the entities */ public Map getEntityHashTable() { - return entityHashTable; + return new HashMap<>(entityHashTable); } /** diff --git a/src/main/java/storage/mapdb/MerkleTreeStateMapDb.java b/src/main/java/storage/mapdb/MerkleTreeStateMapDb.java index 82b35a3f..94c0173d 100644 --- a/src/main/java/storage/mapdb/MerkleTreeStateMapDb.java +++ b/src/main/java/storage/mapdb/MerkleTreeStateMapDb.java @@ -3,6 +3,7 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.ObjectInputStream; +import java.io.Serializable; import java.util.ArrayList; import java.util.concurrent.locks.ReentrantReadWriteLock; @@ -16,7 +17,7 @@ /** * StateMapDb implementation for on-disk MerkleTreeState storage. */ -public class MerkleTreeStateMapDb implements MerkleTreeStates { +public class MerkleTreeStateMapDb implements MerkleTreeStates, Serializable { private final DB db; private final ReentrantReadWriteLock lock; private static final String MAP_NAME = "merkle_tree_state_map"; From f111385c4ce719ef14308e3e6f991390c6f51ec7 Mon Sep 17 00:00:00 2001 From: Ozan Date: Wed, 27 Apr 2022 16:45:18 +0300 Subject: [PATCH 11/12] supresses spotbugs errors --- src/main/java/modules/ads/AuthenticatedEntity.java | 4 +++- .../modules/ads/merkletree/MerkleTreeAuthenticatedEntity.java | 4 +++- .../ads/merkletree/MerkleTreeAuthenticatedEntityVerifier.java | 3 ++- src/main/java/modules/ads/merkletree/MerkleTreeState.java | 1 - 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/java/modules/ads/AuthenticatedEntity.java b/src/main/java/modules/ads/AuthenticatedEntity.java index 449aa167..03b42d61 100644 --- a/src/main/java/modules/ads/AuthenticatedEntity.java +++ b/src/main/java/modules/ads/AuthenticatedEntity.java @@ -1,12 +1,14 @@ package modules.ads; +import java.io.Serializable; + import model.Entity; /** * AuthenticatedEntity is a wrapper model around the Entity type that also contains a membership Merkle Proof for * that entity against a root identifier. */ -public abstract class AuthenticatedEntity extends Entity { +public abstract class AuthenticatedEntity extends Entity implements Serializable { public abstract Entity getEntity(); public abstract MembershipProof getMembershipProof(); diff --git a/src/main/java/modules/ads/merkletree/MerkleTreeAuthenticatedEntity.java b/src/main/java/modules/ads/merkletree/MerkleTreeAuthenticatedEntity.java index a6aa2f33..aee9b216 100644 --- a/src/main/java/modules/ads/merkletree/MerkleTreeAuthenticatedEntity.java +++ b/src/main/java/modules/ads/merkletree/MerkleTreeAuthenticatedEntity.java @@ -1,12 +1,14 @@ package modules.ads.merkletree; +import java.io.Serializable; + import model.Entity; import modules.ads.MembershipProof; /** * An entity with its membership proof and type. */ -public class MerkleTreeAuthenticatedEntity extends modules.ads.AuthenticatedEntity { +public class MerkleTreeAuthenticatedEntity extends modules.ads.AuthenticatedEntity implements Serializable { private final MembershipProof membershipProof; private final String type; private final Entity entity; diff --git a/src/main/java/modules/ads/merkletree/MerkleTreeAuthenticatedEntityVerifier.java b/src/main/java/modules/ads/merkletree/MerkleTreeAuthenticatedEntityVerifier.java index bdea1990..a62a1eda 100644 --- a/src/main/java/modules/ads/merkletree/MerkleTreeAuthenticatedEntityVerifier.java +++ b/src/main/java/modules/ads/merkletree/MerkleTreeAuthenticatedEntityVerifier.java @@ -1,5 +1,6 @@ package modules.ads.merkletree; +import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; @@ -11,7 +12,7 @@ /** * Verifies the AuthenticatedEntity against its self-contained proof. */ -public class MerkleTreeAuthenticatedEntityVerifier implements modules.ads.AuthenticatedEntityVerifier { +public class MerkleTreeAuthenticatedEntityVerifier implements modules.ads.AuthenticatedEntityVerifier, Serializable { /** * Verifies the AuthenticatedEntity against its self-contained proof. diff --git a/src/main/java/modules/ads/merkletree/MerkleTreeState.java b/src/main/java/modules/ads/merkletree/MerkleTreeState.java index d51ad4b7..b4bb6c04 100644 --- a/src/main/java/modules/ads/merkletree/MerkleTreeState.java +++ b/src/main/java/modules/ads/merkletree/MerkleTreeState.java @@ -12,7 +12,6 @@ import model.Entity; import model.crypto.Sha3256Hash; import model.lightchain.Identifier; -import storage.MerkleTreeStates; /** * This class represents the state of the Merkle Tree. From a87ef27d990c02fcc97d63611dbe0e5c9eb21ef7 Mon Sep 17 00:00:00 2001 From: Ozan Date: Wed, 27 Apr 2022 16:49:44 +0300 Subject: [PATCH 12/12] supresses spotbugs errors --- src/main/java/modules/ads/MembershipProof.java | 3 ++- src/main/java/modules/ads/merkletree/MerkleProof.java | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/modules/ads/MembershipProof.java b/src/main/java/modules/ads/MembershipProof.java index 724792ff..9221d96e 100644 --- a/src/main/java/modules/ads/MembershipProof.java +++ b/src/main/java/modules/ads/MembershipProof.java @@ -1,5 +1,6 @@ package modules.ads; +import java.io.Serializable; import java.util.ArrayList; import model.crypto.Sha3256Hash; @@ -7,7 +8,7 @@ /** * Represents a Merkle Proof of membership against a certain root identifier. */ -public interface MembershipProof { +public interface MembershipProof extends Serializable { /** * Root of the authenticated data structure that this proof belongs to. * diff --git a/src/main/java/modules/ads/merkletree/MerkleProof.java b/src/main/java/modules/ads/merkletree/MerkleProof.java index c0b3e8c4..a86f62f1 100644 --- a/src/main/java/modules/ads/merkletree/MerkleProof.java +++ b/src/main/java/modules/ads/merkletree/MerkleProof.java @@ -1,5 +1,6 @@ package modules.ads.merkletree; +import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.Objects; @@ -11,7 +12,7 @@ /** * A proof of membership in a Merkle tree. */ -public class MerkleProof implements MembershipProof { +public class MerkleProof implements MembershipProof, Serializable { private ArrayList path; private final ArrayList isLeftNode; private final Sha3256Hash root;