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/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/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/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/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/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/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/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/MerkleNode.java b/src/main/java/modules/ads/merkletree/MerkleNode.java index 4551b1bf..75148985 100644 --- a/src/main/java/modules/ads/merkletree/MerkleNode.java +++ b/src/main/java/modules/ads/merkletree/MerkleNode.java @@ -1,5 +1,10 @@ package modules.ads.merkletree; +import java.io.Serializable; +import java.nio.charset.StandardCharsets; +import java.util.Objects; + +import com.google.gson.Gson; import crypto.Sha3256Hasher; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import model.Entity; @@ -8,7 +13,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; @@ -70,34 +75,67 @@ 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. + */ @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; } @@ -114,4 +152,44 @@ public MerkleNode getSibling() { return parent.getLeft(); } } + + /** + * 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) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MerkleNode that = (MerkleNode) 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); + return bytes; + } } 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; diff --git a/src/main/java/modules/ads/merkletree/MerkleTree.java b/src/main/java/modules/ads/merkletree/MerkleTree.java index 2c194c4a..038373e5 100644 --- a/src/main/java/modules/ads/merkletree/MerkleTree.java +++ b/src/main/java/modules/ads/merkletree/MerkleTree.java @@ -1,92 +1,38 @@ package modules.ads.merkletree; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectOutputStream; import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.locks.ReentrantReadWriteLock; -import crypto.Sha3256Hasher; import model.Entity; import model.crypto.Sha3256Hash; import model.lightchain.Identifier; -import modules.ads.AuthenticatedDataStructure; /** - * Implementation of an in-memory Authenticated Skip List - * that is capable of storing and retrieval of LightChain entities. + * MerkleTree interface. */ -public class MerkleTree implements AuthenticatedDataStructure { - private static final Sha3256Hasher hasher = new Sha3256Hasher(); - private final ReentrantReadWriteLock lock; - private final ArrayList leafNodes; - private final Map leafNodesHashTable; - private final Map entityHashTable; - private int size; - private MerkleNode root; +public interface MerkleTree { /** - * Default constructor for a Merkle Tree. + * 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 */ - public MerkleTree() { - this.size = 0; - this.root = new MerkleNode(); - this.leafNodes = new ArrayList<>(); - this.lock = new ReentrantReadWriteLock(); - this.leafNodesHashTable = new HashMap<>(); - this.entityHashTable = new HashMap<>(); - } - - @Override - public modules.ads.AuthenticatedEntity put(Entity e) throws IllegalArgumentException { - try { - lock.writeLock().lock(); - if (e == null) { - throw new IllegalArgumentException("entity cannot be null"); - } - Sha3256Hash hash = new Sha3256Hash(e.id().getBytes()); - Integer idx = leafNodesHashTable.get(hash); - if (idx == null) { - leafNodes.add(new MerkleNode(e, false)); - leafNodesHashTable.put(hash, size); - entityHashTable.put(e.id(), e); - size++; - buildMerkleTree(); - MerkleProof proof = getProof(e.id()); - return new MerkleTreeAuthenticatedEntity(proof, e.type(), e); - } else { - MerkleProof proof = getProof(e.id()); - return new MerkleTreeAuthenticatedEntity(proof, e.type(), e); - } - } finally { - lock.writeLock().unlock(); - } - } - - @Override - public modules.ads.AuthenticatedEntity get(Identifier id) throws IllegalArgumentException { - MerkleProof proof; - if (id == null) { - throw new IllegalArgumentException("identifier cannot be null"); - } - try { - lock.readLock().lock(); - proof = getProof(id); - Entity e = entityHashTable.get(id); - return new MerkleTreeAuthenticatedEntity(proof, e.type(), e); - } finally { - lock.readLock().unlock(); - } - } - - private MerkleProof getProof(Identifier id) throws IllegalArgumentException { + default MerkleProof getProof(Identifier id, MerkleTreeState state, MerkleNode root) 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()); @@ -95,42 +41,71 @@ private MerkleProof getProof(Identifier id) throws IllegalArgumentException { 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); + /** + * 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); + if (idx == -1) { + state.addLeafNode(new MerkleNode(e, false)); + state.putLeafNodeHash(hash, size); + state.putEntityHashTable(e.id(), e); + } + return state; + } - // keeps nodes of the next level of merkle tree - // used as an intermediary data structure. - ArrayList nextLevelNodes = new ArrayList<>(); + /** + * 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; - 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); + /** + * 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; - 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<>(); + /** + * Returns the byte array of the merkle tree. + * + * @return the byte array of the merkle tree + */ + 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(); } - root = currentLevelNodes.get(0); + return null; } - public int size() { - return this.size; - } + /** + * Returns the size of the ADS. + * + * @return the size of the ADS + */ + int size(); + } 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/MerkleTreeInMemoryState.java b/src/main/java/modules/ads/merkletree/MerkleTreeInMemoryState.java new file mode 100644 index 00000000..812eacb1 --- /dev/null +++ b/src/main/java/modules/ads/merkletree/MerkleTreeInMemoryState.java @@ -0,0 +1,134 @@ +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; +import model.lightchain.Identifier; +import modules.ads.AuthenticatedDataStructure; + +/** + * Implementation of an in-memory Authenticated Skip List + * that is capable of storing and retrieval of LightChain entities. + */ +public class MerkleTreeInMemoryState implements AuthenticatedDataStructure, Serializable, MerkleTree { + private static final Sha3256Hasher hasher = new Sha3256Hasher(); + private final ReentrantReadWriteLock lock; + private MerkleTreeState state; + private int size; + private MerkleNode root; + + /** + * Default constructor for an in memory Merkle Tree. + */ + public MerkleTreeInMemoryState() { + this.size = 0; + this.root = new MerkleNode(); + this.lock = new ReentrantReadWriteLock(); + 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(); + if (e == null) { + throw new IllegalArgumentException("entity cannot be null"); + } + int idx = state.getNodeIndex(new Sha3256Hash(e.id().getBytes())); + if (idx == -1) { + this.state = put(e, state, size); + 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; + } + + public MerkleTreeState getState() { + 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 new file mode 100644 index 00000000..5ad70989 --- /dev/null +++ b/src/main/java/modules/ads/merkletree/MerkleTreeOnDiskState.java @@ -0,0 +1,141 @@ +package modules.ads.merkletree; + +import java.io.Serializable; +import java.util.ArrayList; +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; +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; + private MerkleTreeStateMapDb stateMapDb; + private int size; + private MerkleNode root; + private MerkleTreeState state; + + /** + * 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(); + 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/modules/ads/merkletree/MerkleTreeState.java b/src/main/java/modules/ads/merkletree/MerkleTreeState.java new file mode 100644 index 00000000..b4bb6c04 --- /dev/null +++ b/src/main/java/modules/ads/merkletree/MerkleTreeState.java @@ -0,0 +1,181 @@ +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.Objects; + +import model.Entity; +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. + */ + 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 new ArrayList<>(leafNodes); + } + + /** + * Returns the hash table of the leaf nodes. + * + * @return the hash table of the leaf nodes + */ + public Map getLeafNodesHashTable() { + return new HashMap<>(leafNodesHashTable); + } + + /** + * Returns the hash table of the entities. + * + * @return the hash table of the entities + */ + public Map getEntityHashTable() { + return new HashMap<>(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); + } + 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); + } + 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; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MerkleTreeState that = (MerkleTreeState) 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(); + 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..5e075d6e 100644 --- a/src/main/java/modules/codec/JsonEncoder.java +++ b/src/main/java/modules/codec/JsonEncoder.java @@ -1,26 +1,34 @@ package modules.codec; -import java.lang.reflect.Type; -import java.nio.charset.StandardCharsets; +import java.io.*; -import com.google.gson.Gson; import model.Entity; import model.codec.EncodedEntity; /** * Implements encoding and decoding using JSON. */ -public class JsonEncoder implements Codec { +public class JsonEncoder implements Codec, Serializable { /** * Encodes an Entity to an EncodedEntity. * * @param e input Entity. + * * @return the JSON encoded representation of Entity. */ @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); } @@ -29,13 +37,22 @@ public EncodedEntity encode(Entity e) { * Decodes a JSON EncodedEntity to its original Entity type. * * @param e input JSON EncodedEntity. + * * @return original Entity type. */ @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/MerkleTreeStates.java b/src/main/java/storage/MerkleTreeStates.java new file mode 100644 index 00000000..87d699d4 --- /dev/null +++ b/src/main/java/storage/MerkleTreeStates.java @@ -0,0 +1,45 @@ +package storage; + +import java.util.ArrayList; + +import modules.ads.merkletree.MerkleTreeState; + +/** + * Interface for the Merkle Tree States. + */ +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/MerkleTreeStateMapDb.java b/src/main/java/storage/mapdb/MerkleTreeStateMapDb.java new file mode 100644 index 00000000..94c0173d --- /dev/null +++ b/src/main/java/storage/mapdb/MerkleTreeStateMapDb.java @@ -0,0 +1,155 @@ +package storage.mapdb; + +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; + +import modules.ads.merkletree.MerkleTreeState; +import org.mapdb.DB; +import org.mapdb.DBMaker; +import org.mapdb.HTreeMap; +import org.mapdb.Serializer; +import storage.MerkleTreeStates; + +/** + * StateMapDb implementation for on-disk MerkleTreeState storage. + */ +public class MerkleTreeStateMapDb implements MerkleTreeStates, Serializable { + 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 the state1 and adds the state2. + * + * @param state1 the state to be removed. + * @param state2 the state to be added. + * + * @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); + } + + /** + * 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(); + } + + /** + * 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..e38d3da2 --- /dev/null +++ b/src/test/java/modules/ads/MerkleTreeInMemoryStateTest.java @@ -0,0 +1,83 @@ +package modules.ads; + +import org.junit.jupiter.api.Test; + +/** + * Tests for MerkleTreeInMemoryState. + */ +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..99a654cd --- /dev/null +++ b/src/test/java/modules/ads/MerkleTreeOnDiskStateTest.java @@ -0,0 +1,130 @@ +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; + +/** + * Tests for MerkleTreeOnDiskState. + */ +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 95a290dc..0a75037f 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; @@ -13,10 +14,7 @@ import modules.ads.merkletree.MerkleTreeAuthenticatedEntity; import modules.ads.merkletree.MerkleTreeAuthenticatedEntityVerifier; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -import unittest.fixtures.EntityFixture; -import unittest.fixtures.MerkleTreeFixture; -import unittest.fixtures.Sha3256HashFixture; +import unittest.fixtures.*; /** * Encapsulates tests for an authenticated and concurrent implementation of MerkleTree ADS. @@ -24,14 +22,15 @@ public class MerkleTreeTest { /** - * A basic test for one sequential put and get operations. + * 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 */ - @Test - public void testVerification() { - MerkleTree merkleTree = MerkleTreeFixture.createMerkleTree(5); + public static void testVerification(Entity entity, MerkleTree merkleTree) { + entity = entity != null ? entity : new EntityFixture(); + merkleTree = merkleTree != null ? merkleTree : MerkleTreeFixture.createInMemoryStateMerkleTree(5); Assertions.assertEquals(merkleTree.size(), 5); // fixture sanity check. - - Entity entity = new EntityFixture(); merkleTree.put(entity); Assertions.assertEquals(merkleTree.size(), 6); @@ -41,14 +40,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.createInMemoryStateMerkleTree(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 +76,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.createInMemoryStateMerkleTree(5); Assertions.assertEquals(merkleTree.size(), 5); // fixture sanity check. - Entity entity = new EntityFixture(); // first time put AuthenticatedEntity authenticatedEntityPut = merkleTree.put(entity); @@ -98,10 +101,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 +119,17 @@ public void testConcurrentPutGet() { Thread[] putThreads = new Thread[concurrencyDegree]; Thread[] getThreads = new Thread[concurrencyDegree]; - MerkleTree merkleTree = MerkleTreeFixture.createMerkleTree(0); + 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++) { - Entity entity = new EntityFixture(); + Entity entity; + if (type == EntityType.TYPE_ACCOUNT) { + entity = AccountFixture.newAccount(IdentifierFixture.newIdentifier()); + } else { + entity = new EntityFixture(); + } entities.add(entity); ids.add(entity.id()); } @@ -127,7 +139,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 +161,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,39 +186,43 @@ 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.createInMemoryStateMerkleTree(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.createInMemoryStateMerkleTree(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.createInMemoryStateMerkleTree(5); Assertions.assertEquals(merkleTree.size(), 5); // fixture sanity check. - Entity entity = new EntityFixture(); AuthenticatedEntity authenticatedEntity = merkleTree.put(entity); MembershipProof proof = authenticatedEntity.getMembershipProof(); @@ -225,13 +241,15 @@ 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.createInMemoryStateMerkleTree(5); Assertions.assertEquals(merkleTree.size(), 5); // fixture sanity check. - Entity entity = new EntityFixture(); AuthenticatedEntity authenticatedEntity = merkleTree.put(entity); AuthenticatedEntity tamperedEntity = new MerkleTreeAuthenticatedEntity( @@ -245,14 +263,15 @@ 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.createInMemoryStateMerkleTree(5); Assertions.assertEquals(merkleTree.size(), 5); // fixture sanity check. - - Entity entity = new EntityFixture(); AuthenticatedEntity authenticatedEntity = merkleTree.put(entity); MembershipProof proof = authenticatedEntity.getMembershipProof(); 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..251ed11c --- /dev/null +++ b/src/test/java/storage/MerkleTreeStatesTest.java @@ -0,0 +1,383 @@ +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 java.util.concurrent.CountDownLatch; +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. + */ +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/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..4acde220 100644 --- a/src/test/java/unittest/fixtures/MerkleTreeFixture.java +++ b/src/test/java/unittest/fixtures/MerkleTreeFixture.java @@ -1,22 +1,42 @@ package unittest.fixtures; -import modules.ads.merkletree.MerkleTree; +import java.util.ArrayList; + +import modules.ads.merkletree.MerkleTreeInMemoryState; /** * Creates a new randomly looking MerkleTree. */ 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. */ - public static MerkleTree createMerkleTree(int n) { - MerkleTree merkleTree = new MerkleTree(); + public static MerkleTreeInMemoryState createInMemoryStateMerkleTree(int n) { + MerkleTreeInMemoryState merkleTree = new MerkleTreeInMemoryState(); for (int i = 0; i < n; i++) { merkleTree.put(new EntityFixture()); } 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(createInMemoryStateMerkleTree(m)); + } + return trees; + } } diff --git a/src/test/java/unittest/fixtures/MerkleTreeStateFixture.java b/src/test/java/unittest/fixtures/MerkleTreeStateFixture.java new file mode 100644 index 00000000..d7e1790e --- /dev/null +++ b/src/test/java/unittest/fixtures/MerkleTreeStateFixture.java @@ -0,0 +1,36 @@ +package unittest.fixtures; + +import java.util.ArrayList; + +import modules.ads.merkletree.MerkleTreeInMemoryState; +import modules.ads.merkletree.MerkleTreeState; + +/** + * 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++) { + 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..a4c9097f --- /dev/null +++ b/src/test/java/unittest/fixtures/MerkleTreeStateMapDbFixture.java @@ -0,0 +1,25 @@ +package unittest.fixtures; + +import java.io.IOException; +import java.nio.file.Path; + +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); + } +}