diff --git a/src/main/java/com/iota/iri/service/snapshot/SnapshotService.java b/src/main/java/com/iota/iri/service/snapshot/SnapshotService.java
index 5bb3d56aa2..5770e76254 100644
--- a/src/main/java/com/iota/iri/service/snapshot/SnapshotService.java
+++ b/src/main/java/com/iota/iri/service/snapshot/SnapshotService.java
@@ -1,11 +1,9 @@
package com.iota.iri.service.snapshot;
-import com.iota.iri.conf.SnapshotConfig;
import com.iota.iri.controllers.MilestoneViewModel;
import com.iota.iri.model.Hash;
import com.iota.iri.service.milestone.LatestMilestoneTracker;
import com.iota.iri.service.transactionpruning.TransactionPruner;
-import com.iota.iri.storage.Tangle;
import java.util.Map;
diff --git a/src/main/java/com/iota/iri/service/snapshot/impl/LocalSnapshotManagerImpl.java b/src/main/java/com/iota/iri/service/snapshot/impl/LocalSnapshotManagerImpl.java
index d55cfdcc1a..92be47f133 100644
--- a/src/main/java/com/iota/iri/service/snapshot/impl/LocalSnapshotManagerImpl.java
+++ b/src/main/java/com/iota/iri/service/snapshot/impl/LocalSnapshotManagerImpl.java
@@ -1,5 +1,6 @@
package com.iota.iri.service.snapshot.impl;
+import com.google.common.annotations.VisibleForTesting;
import com.iota.iri.conf.SnapshotConfig;
import com.iota.iri.service.milestone.LatestMilestoneTracker;
import com.iota.iri.service.snapshot.LocalSnapshotManager;
@@ -31,7 +32,8 @@ public class LocalSnapshotManagerImpl implements LocalSnapshotManager {
* To prevent jumping back and forth in and out of sync, there is a buffer in between.
* Only when the latest milestone and latest snapshot differ more than this number, we fall out of sync
*/
- private static final int LOCAL_SNAPSHOT_SYNC_BUFFER = 5;
+ @VisibleForTesting
+ static final int LOCAL_SNAPSHOT_SYNC_BUFFER = 5;
/**
* Logger for this class allowing us to dump debug and status messages.
@@ -128,7 +130,8 @@ public void shutdown() {
*
* @param latestMilestoneTracker tracker for the milestones to determine when a new local snapshot is due
*/
- private void monitorThread(LatestMilestoneTracker latestMilestoneTracker) {
+ @VisibleForTesting
+ void monitorThread(LatestMilestoneTracker latestMilestoneTracker) {
while (!Thread.currentThread().isInterrupted()) {
int localSnapshotInterval = getSnapshotInterval(isInSync(latestMilestoneTracker));
@@ -154,38 +157,42 @@ private void monitorThread(LatestMilestoneTracker latestMilestoneTracker) {
* @param inSync if this node is in sync
* @return the current interval in which we take local snapshots
*/
- private int getSnapshotInterval(boolean inSync) {
+ @VisibleForTesting
+ int getSnapshotInterval(boolean inSync) {
return inSync
? config.getLocalSnapshotsIntervalSynced()
: config.getLocalSnapshotsIntervalUnsynced();
}
-
+
/**
- * A node is defined in sync when the latest snapshot milestone index and the latest milestone index are equal.
- * In order to prevent a bounce between in and out of sync, a buffer is added when a node became in sync.
+ * A node is defined in sync when the latest snapshot milestone index and the
+ * latest milestone index are equal. In order to prevent a bounce between in and
+ * out of sync, a buffer is added when a node became in sync.
*
- * This will always return false if we are not done scanning milestone candidates during initialization.
+ * This will always return false if we are not done scanning milestone
+ * candidates during initialization.
*
* @param latestMilestoneTracker tracker we use to determine milestones
* @return true
if we are in sync, otherwise false
*/
- private boolean isInSync(LatestMilestoneTracker latestMilestoneTracker) {
+ @VisibleForTesting
+ boolean isInSync(LatestMilestoneTracker latestMilestoneTracker) {
if (!latestMilestoneTracker.isInitialScanComplete()) {
return false;
}
-
+
int latestIndex = latestMilestoneTracker.getLatestMilestoneIndex();
int latestSnapshot = snapshotProvider.getLatestSnapshot().getIndex();
-
+
// If we are out of sync, only a full sync will get us in
if (!isInSync && latestIndex == latestSnapshot) {
isInSync = true;
-
- // When we are in sync, only dropping below the buffer gets us out of sync
+
+ // When we are in sync, only dropping below the buffer gets us out of sync
} else if (latestSnapshot < latestIndex - LOCAL_SNAPSHOT_SYNC_BUFFER) {
isInSync = false;
}
-
+
return isInSync;
}
}
diff --git a/src/main/java/com/iota/iri/service/snapshot/impl/SnapshotProviderImpl.java b/src/main/java/com/iota/iri/service/snapshot/impl/SnapshotProviderImpl.java
index 9114f8333b..c0b53b35ef 100644
--- a/src/main/java/com/iota/iri/service/snapshot/impl/SnapshotProviderImpl.java
+++ b/src/main/java/com/iota/iri/service/snapshot/impl/SnapshotProviderImpl.java
@@ -1,5 +1,6 @@
package com.iota.iri.service.snapshot.impl;
+import com.google.common.annotations.VisibleForTesting;
import com.iota.iri.SignedFiles;
import com.iota.iri.conf.SnapshotConfig;
import com.iota.iri.model.Hash;
@@ -62,7 +63,8 @@ public class SnapshotProviderImpl implements SnapshotProvider {
* snapshot multiple times while creating their own version of the LocalSnapshotManager, we cache the instance
* here so they don't have to rebuild it from the scratch every time (massively speeds up the unit tests).
*/
- private static SnapshotImpl builtinSnapshot = null;
+ @VisibleForTesting
+ static SnapshotImpl builtinSnapshot = null;
/**
* Holds Snapshot related configuration parameters.
@@ -340,7 +342,7 @@ private SnapshotState readSnapshotStatefromFile(String snapshotStateFilePath) th
private SnapshotState readSnapshotStateFromJAR(String snapshotStateFilePath) throws SnapshotException {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(new BufferedInputStream(SnapshotProviderImpl.class.getResourceAsStream(snapshotStateFilePath))))) {
return readSnapshotState(reader);
- } catch (IOException e) {
+ } catch (NullPointerException | IOException e) {
throw new SnapshotException("failed to read the snapshot file from JAR at " + snapshotStateFilePath, e);
}
}
diff --git a/src/main/java/com/iota/iri/service/snapshot/impl/SnapshotStateImpl.java b/src/main/java/com/iota/iri/service/snapshot/impl/SnapshotStateImpl.java
index ef6cd3868a..3a0ba36d1a 100644
--- a/src/main/java/com/iota/iri/service/snapshot/impl/SnapshotStateImpl.java
+++ b/src/main/java/com/iota/iri/service/snapshot/impl/SnapshotStateImpl.java
@@ -2,18 +2,12 @@
import com.iota.iri.controllers.TransactionViewModel;
import com.iota.iri.model.Hash;
-import com.iota.iri.model.HashFactory;
import com.iota.iri.service.snapshot.SnapshotException;
import com.iota.iri.service.snapshot.SnapshotState;
import com.iota.iri.service.snapshot.SnapshotStateDiff;
-import com.iota.iri.utils.IotaIOUtils;
-
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.io.*;
-import java.nio.file.Files;
-import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
diff --git a/src/test/java/com/iota/iri/service/snapshot/impl/LocalSnapshotManagerImplTest.java b/src/test/java/com/iota/iri/service/snapshot/impl/LocalSnapshotManagerImplTest.java
new file mode 100644
index 0000000000..b1dd52bbb5
--- /dev/null
+++ b/src/test/java/com/iota/iri/service/snapshot/impl/LocalSnapshotManagerImplTest.java
@@ -0,0 +1,149 @@
+package com.iota.iri.service.snapshot.impl;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.any;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.exceptions.base.MockitoAssertionError;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import com.iota.iri.conf.SnapshotConfig;
+import com.iota.iri.service.milestone.LatestMilestoneTracker;
+import com.iota.iri.service.snapshot.SnapshotException;
+import com.iota.iri.service.snapshot.SnapshotProvider;
+import com.iota.iri.service.snapshot.SnapshotService;
+import com.iota.iri.service.transactionpruning.TransactionPruner;
+import com.iota.iri.utils.thread.ThreadUtils;
+
+public class LocalSnapshotManagerImplTest {
+
+ private static final int BUFFER = LocalSnapshotManagerImpl.LOCAL_SNAPSHOT_SYNC_BUFFER;
+
+ private static final int DELAY_SYNC = 5;
+ private static final int DELAY_UNSYNC = 1;
+ private static final int SNAPSHOT_DEPTH = 5;
+
+ @Rule
+ public MockitoRule mockitoRule = MockitoJUnit.rule();
+
+ @Mock
+ private static SnapshotConfig config;
+
+
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ SnapshotProvider snapshotProvider;
+
+ @Mock
+ SnapshotService snapshotService;
+
+ @Mock
+ TransactionPruner transactionPruner;
+
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ LatestMilestoneTracker milestoneTracker;
+
+ private LocalSnapshotManagerImpl lsManager;
+
+ @Before
+ public void setUp() throws Exception {
+ this.lsManager = new LocalSnapshotManagerImpl();
+
+ lsManager.init(snapshotProvider, snapshotService, transactionPruner, config);
+ when(snapshotProvider.getLatestSnapshot().getIndex()).thenReturn(-5, -1, 10, 998, 999, 1999, 2000);
+
+ when(config.getLocalSnapshotsIntervalSynced()).thenReturn(DELAY_SYNC);
+ when(config.getLocalSnapshotsIntervalUnsynced()).thenReturn(DELAY_UNSYNC);
+ when(config.getLocalSnapshotsDepth()).thenReturn(SNAPSHOT_DEPTH);
+ }
+
+ @After
+ public void tearDown() {
+ lsManager.shutdown();
+ }
+
+ @Test
+ public synchronized void takeLocalSnapshot() throws SnapshotException {
+ // Always return true
+ when(milestoneTracker.isInitialScanComplete()).thenReturn(true);
+
+ // When we call it, we are in sync
+ when(milestoneTracker.getLatestMilestoneIndex()).thenReturn(-5);
+
+ // We are more then the depth ahead
+ when(snapshotProvider.getLatestSnapshot().getIndex()).thenReturn(100);
+ when(snapshotProvider.getInitialSnapshot().getIndex()).thenReturn(100 - SNAPSHOT_DEPTH - DELAY_SYNC - 1);
+
+ // Run in separate thread to allow us to time-out
+ Thread t = new Thread(() -> lsManager.monitorThread(milestoneTracker));
+
+ t.start();
+ // We should finish directly, margin for slower computers
+ ThreadUtils.sleep(100);
+
+ // Cancel the thread
+ t.interrupt();
+
+ // Verify we took a snapshot
+ try {
+ verify(snapshotService, times(1)).takeLocalSnapshot(any(), any());
+ } catch (MockitoAssertionError e) {
+ throw new MockitoAssertionError("A snapshot should have been taken when we are below SNAPSHOT_DEPTH");
+ }
+ }
+
+ @Test
+ public void isInSyncTestScanIncomplete() {
+ when(milestoneTracker.isInitialScanComplete()).thenReturn(false);
+
+ assertFalse("We should be out of sync when he havent finished initial scans", lsManager.isInSync(milestoneTracker));
+ }
+
+ @Test
+ public void isInSyncTestScanComplete() {
+ // Always return true
+ when(milestoneTracker.isInitialScanComplete()).thenReturn(true);
+
+ // We don't really support -1 indexes, but if this breaks, it is a good indication to be careful going further
+ when(milestoneTracker.getLatestMilestoneIndex()).thenReturn(-1, 5, 10, 998 + BUFFER - 1, 2000);
+
+ // snapshotProvider & milestoneTracker
+ // -5 & -1 -> not in sync
+ assertFalse("Previous out of sync and not equal index should not be in sync", lsManager.isInSync(milestoneTracker));
+
+ // -1 and 5 -> not in sync
+ assertFalse("Previous out of sync and not equal index should not be in sync", lsManager.isInSync(milestoneTracker));
+
+ // 10 and 10 -> in sync
+ assertTrue("Equal index should be in sync", lsManager.isInSync(milestoneTracker));
+
+ // 998 and 1002 -> in sync since sync gap = 5
+ assertTrue("Index difference less than the buffer still should be in sync", lsManager.isInSync(milestoneTracker));
+
+ // 999 and 2000 -> out of sync again, bigger gap than 5
+ assertFalse("Index difference more than the buffer should be out of sync again ", lsManager.isInSync(milestoneTracker));
+
+ // 1999 and 2000 -> out of sync still
+ assertFalse("Previous out of sync and not equal index should not be in sync", lsManager.isInSync(milestoneTracker));
+
+ // 2000 and 2000 -> in sync again
+ assertTrue("Equal index should be in sync", lsManager.isInSync(milestoneTracker));
+ }
+
+ @Test
+ public void getDelayTest() {
+ assertEquals("Out of sync should return the config value at getLocalSnapshotsIntervalUnsynced",
+ DELAY_UNSYNC, lsManager.getSnapshotInterval(false));
+
+ assertEquals("In sync should return the config value at getLocalSnapshotsIntervalSynced",
+ DELAY_SYNC, lsManager.getSnapshotInterval(true));
+ }
+}
diff --git a/src/test/java/com/iota/iri/service/snapshot/impl/SnapshotImplTest.java b/src/test/java/com/iota/iri/service/snapshot/impl/SnapshotImplTest.java
new file mode 100644
index 0000000000..2ac294fa06
--- /dev/null
+++ b/src/test/java/com/iota/iri/service/snapshot/impl/SnapshotImplTest.java
@@ -0,0 +1,68 @@
+package com.iota.iri.service.snapshot.impl;
+
+import static org.junit.Assert.*;
+
+import java.util.HashMap;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.iota.iri.TransactionTestUtils;
+import com.iota.iri.model.Hash;
+import com.iota.iri.service.snapshot.Snapshot;
+import com.iota.iri.service.snapshot.SnapshotMetaData;
+import com.iota.iri.service.snapshot.SnapshotState;
+
+public class SnapshotImplTest {
+
+ private static SnapshotState state;
+
+ private static SnapshotMetaData metaData;
+
+ @Before
+ public void setUp() throws Exception {
+ state = new SnapshotStateImpl(new HashMap<>());
+ metaData = new SnapshotMetaDataImpl(Hash.NULL_HASH, 1, 1l, new HashMap<>(), new HashMap<>());
+ }
+
+ @Test
+ public void skippedMilestoneTest() {
+ Snapshot snapshot = new SnapshotImpl(state, metaData);
+ assertTrue("Not previously seen milestone should be accepted", snapshot.addSkippedMilestone(1));
+
+ assertFalse("Previously seen milestone should not be accepted", snapshot.addSkippedMilestone(1));
+ assertTrue("Skipped milestone should be removed correctly", snapshot.removeSkippedMilestone(1));
+ assertFalse("Not skipped milestone should fail to get removed", snapshot.removeSkippedMilestone(1));
+ }
+
+ @Test
+ public void updateTest() {
+ Snapshot snapshot = new SnapshotImpl(state, metaData);
+ snapshot.setIndex(0);
+ snapshot.setHash(Hash.NULL_HASH);
+ snapshot.setInitialTimestamp(1l);
+
+ Snapshot newSnapshot = snapshot.clone();
+ newSnapshot.setIndex(1);
+ snapshot.setHash(TransactionTestUtils.getTransactionHash());
+ snapshot.setInitialTimestamp(5l);
+
+ assertNotEquals("Modified snapshot clone should not be equal to its original", snapshot, newSnapshot);
+ snapshot.update(newSnapshot);
+ assertEquals("Updating a snapshot with another snapshot should make them equal", snapshot, newSnapshot);
+ }
+
+ @Test
+ public void cloneTest() {
+ Snapshot oldSnapshot = new SnapshotImpl(state, metaData);
+ Snapshot newSnapshot = oldSnapshot.clone();
+
+ assertEquals("A clone of a snapshot is equal to its original", oldSnapshot, newSnapshot);
+
+ oldSnapshot.addSkippedMilestone(1);
+
+ // Clone shouldnt have the skipped milestone
+ assertFalse("Adding a value to a clone should be reflected on the original", newSnapshot.removeSkippedMilestone(1));
+ assertNotEquals("A clone should not be equal to its original after modification", oldSnapshot, newSnapshot);
+ }
+}
diff --git a/src/test/java/com/iota/iri/service/snapshot/impl/SnapshotMetaDataImplTest.java b/src/test/java/com/iota/iri/service/snapshot/impl/SnapshotMetaDataImplTest.java
new file mode 100644
index 0000000000..7e41c4696b
--- /dev/null
+++ b/src/test/java/com/iota/iri/service/snapshot/impl/SnapshotMetaDataImplTest.java
@@ -0,0 +1,124 @@
+package com.iota.iri.service.snapshot.impl;
+
+import static org.junit.Assert.*;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.iota.iri.TransactionTestUtils;
+import com.iota.iri.conf.BaseIotaConfig;
+import com.iota.iri.model.Hash;
+import com.iota.iri.service.snapshot.SnapshotMetaData;
+
+public class SnapshotMetaDataImplTest {
+
+ private static final Hash A = TransactionTestUtils.getTransactionHash();
+ private static final Hash B = TransactionTestUtils.getTransactionHash();
+ private static final Hash C = TransactionTestUtils.getTransactionHash();
+ private static final Hash D = TransactionTestUtils.getTransactionHash();
+
+ private static Map solidEntryPoints = new HashMap(){{
+ put(A, 1);
+ put(B, 2);
+ put(C, -1);
+ }};
+
+ private static Map seenMilestones = new HashMap(){{
+ put(A, 10);
+ put(B, 11);
+ put(C, 12);
+ put(D, 13);
+ }};
+
+ private SnapshotMetaDataImpl meta;
+
+ @Before
+ public void setUp() {
+ meta = new SnapshotMetaDataImpl(A,
+ BaseIotaConfig.Defaults.MILESTONE_START_INDEX,
+ BaseIotaConfig.Defaults.GLOBAL_SNAPSHOT_TIME,
+ solidEntryPoints,
+ seenMilestones);
+ }
+
+ @Test
+ public void initialIndexTest(){
+ assertEquals("Initial index should be equal to the one provided",
+ meta.getInitialIndex(), BaseIotaConfig.Defaults.MILESTONE_START_INDEX);
+ assertEquals("Current index should be equal to the initial index",
+ meta.getIndex(), BaseIotaConfig.Defaults.MILESTONE_START_INDEX);
+
+ meta.setIndex(BaseIotaConfig.Defaults.MILESTONE_START_INDEX + 1);
+ assertNotEquals("Initial index should not be the same as current index after setting",
+ meta.getInitialIndex(), meta.getIndex());
+ }
+
+ @Test
+ public void initialTimestampTest(){
+ assertEquals("Initial timestamp should be equal to the one provided",
+ meta.getInitialTimestamp(), BaseIotaConfig.Defaults.GLOBAL_SNAPSHOT_TIME);
+ assertEquals("Current timestamp should be equal to the initial timestamp",
+ meta.getTimestamp(), BaseIotaConfig.Defaults.GLOBAL_SNAPSHOT_TIME);
+
+ meta.setTimestamp(BaseIotaConfig.Defaults.GLOBAL_SNAPSHOT_TIME + 1);
+ assertNotEquals("Initial timestamp should not be the same as current timestamp after setting",
+ meta.getInitialTimestamp(), meta.getTimestamp());
+ }
+
+ @Test
+ public void hashTest(){
+ assertEquals("Initial hash should be equal to the one provided", meta.getInitialHash(), A);
+
+ assertEquals("Current hash should be equal to the initial hash", meta.getHash(), A);
+
+ meta.setHash(B);
+ assertNotEquals("Initial hash should not be the same as current hash after setting", meta.getInitialHash(), meta.getHash());
+ }
+
+ @Test
+ public void solidEntryPointsTest(){
+ assertTrue("We should have the entry point provided on start", meta.hasSolidEntryPoint(A));
+ assertTrue("We should have the entry point provided on start", meta.hasSolidEntryPoint(B));
+ assertTrue("We should have the entry point provided on start", meta.hasSolidEntryPoint(C));
+
+ assertEquals("Index from entry should be to the one set to the hash", 1, meta.getSolidEntryPointIndex(A));
+ assertEquals("Index from entry should be to the one set to the hash", 2, meta.getSolidEntryPointIndex(B));
+
+ // Test -1 to ensure, if we ever enforce this positive, something could break
+ // We don't really support -1 indexes, but if this breaks, it is a good indication to be careful going further
+ assertEquals("Index from entry should be to the one set to the hash", -1, meta.getSolidEntryPointIndex(C));
+
+ assertEquals("Solid entries amount should be the same as the ones provided", meta.getSolidEntryPoints().size(), solidEntryPoints.size());
+ assertEquals("Solid entries should be the same as the ones provided", meta.getSolidEntryPoints(), new HashMap<>(solidEntryPoints));
+
+ meta.setSolidEntryPoints(seenMilestones);
+ // Right now, the map is replaced, so none are 'new' or 'existing'.
+ assertEquals("Existing entrypoints should have a new index", 10, meta.getSolidEntryPointIndex(A));
+ assertEquals("Existing entrypoints should have a new index", 11, meta.getSolidEntryPointIndex(B));
+ assertEquals("Existing entrypoints should have a new index", 12, meta.getSolidEntryPointIndex(C));
+ assertEquals("New entry point added should exist and equal to the index provided", 13, meta.getSolidEntryPointIndex(D));
+ }
+
+ @Test
+ public void seenMilestonesTest(){
+ assertEquals("Seen milestones amount should be the same as the ones provided",
+ meta.getSeenMilestones().size(), seenMilestones.size());
+ assertEquals("Seen milestones should be the same as the ones provided",
+ meta.getSeenMilestones(), new HashMap<>(seenMilestones));
+ }
+
+ @Test
+ public void createTest(){
+ SnapshotMetaData newMetaData = new SnapshotMetaDataImpl(meta);
+ assertEquals("new SnapshotMetaData made from another should be equal", newMetaData, meta);
+
+ newMetaData = new SnapshotMetaDataImpl(Hash.NULL_HASH, 0, 0l, Collections.EMPTY_MAP, Collections.EMPTY_MAP);
+ newMetaData.update(meta);
+ assertEquals("Updating a SnapshotMetaData with another should make them equal", newMetaData, meta);
+ }
+
+}
diff --git a/src/test/java/com/iota/iri/service/snapshot/impl/SnapshotMockUtils.java b/src/test/java/com/iota/iri/service/snapshot/impl/SnapshotMockUtils.java
index 8d17c1b902..ff7e642ba7 100644
--- a/src/test/java/com/iota/iri/service/snapshot/impl/SnapshotMockUtils.java
+++ b/src/test/java/com/iota/iri/service/snapshot/impl/SnapshotMockUtils.java
@@ -20,20 +20,60 @@ public class SnapshotMockUtils {
//region [mockSnapshotProvider] ////////////////////////////////////////////////////////////////////////////////////
+ /**
+ * Properly imitates a snapshot provider by making a real initial and latest snapshot.
+ * The balance of this provider is made to let the DEFAULT_GENESIS_ADDRESS (Null hash) have the entire IOTA supply.
+ * Genesis timestamp set to {@value #DEFAULT_GENESIS_TIMESTAMP}.
+ * Initial snapshot hash set to DEFAULT_GENESIS_ADDRESS.
+ * Starting index is {@value #DEFAULT_MILESTONE_START_INDEX}
+ *
+ * @param snapshotProvider The provider we are mocking. Must be a Mockito Mocked object
+ * @return The supplied snapshotProvider object
+ */
public static SnapshotProvider mockSnapshotProvider(SnapshotProvider snapshotProvider) {
return mockSnapshotProvider(snapshotProvider, DEFAULT_MILESTONE_START_INDEX);
}
+ /**
+ * Properly imitates a snapshot provider by making a real initial and latest snapshot.
+ * The balance of this provider is made to let the DEFAULT_GENESIS_ADDRESS (Null hash) have the entire IOTA supply.
+ * Genesis timestamp set to {@value #DEFAULT_GENESIS_TIMESTAMP}.
+ * Initial snapshot hash set to DEFAULT_GENESIS_ADDRESS.
+ *
+ * @param snapshotProvider The provider we are mocking. Must be a Mockito Mocked object
+ * @param milestoneStartIndex The index we use for the genesis/initial snapshot
+ * @return The supplied snapshotProvider object
+ */
public static SnapshotProvider mockSnapshotProvider(SnapshotProvider snapshotProvider, int milestoneStartIndex) {
return mockSnapshotProvider(snapshotProvider, milestoneStartIndex, DEFAULT_GENESIS_HASH);
}
+ /**
+ * Properly imitates a snapshot provider by making a real initial and latest snapshot.
+ * The balance of this provider is made to let the DEFAULT_GENESIS_ADDRESS (Null hash) have the entire IOTA supply.
+ * Genesis timestamp set to {@value #DEFAULT_GENESIS_TIMESTAMP}
+ *
+ * @param snapshotProvider The provider we are mocking. Must be a Mockito Mocked object
+ * @param milestoneStartIndex The index we use for the genesis/initial snapshot
+ * @param genesisHash The Genesis hash
+ * @return The supplied snapshotProvider object
+ */
public static SnapshotProvider mockSnapshotProvider(SnapshotProvider snapshotProvider, int milestoneStartIndex,
Hash genesisHash) {
return mockSnapshotProvider(snapshotProvider, milestoneStartIndex, genesisHash, DEFAULT_GENESIS_TIMESTAMP);
}
+ /**
+ * Properly imitates a snapshot provider by making a real initial and latest snapshot.
+ * The balance of this provider is made to let the DEFAULT_GENESIS_ADDRESS (Null hash) have the entire IOTA supply.
+ *
+ * @param snapshotProvider The provider we are mocking. Must be a Mockito Mocked object
+ * @param milestoneStartIndex The index we use for the genesis/initial snapshot
+ * @param genesisHash The Genesis hash
+ * @param genesisTimestamp The timestamp of the initial snapshot creation
+ * @return The supplied snapshotProvider object
+ */
public static SnapshotProvider mockSnapshotProvider(SnapshotProvider snapshotProvider, int milestoneStartIndex,
Hash genesisHash, long genesisTimestamp) {
@@ -43,6 +83,16 @@ public static SnapshotProvider mockSnapshotProvider(SnapshotProvider snapshotPro
return mockSnapshotProvider(snapshotProvider, milestoneStartIndex, genesisHash, genesisTimestamp, balances);
}
+ /**
+ * Properly imitates a snapshot provider by making a real initial and latest snapshot
+ *
+ * @param snapshotProvider The provider we are mocking. Must be a Mockito Mocked object
+ * @param milestoneStartIndex The index we use for the genesis/initial snapshot
+ * @param genesisHash The Genesis hash
+ * @param genesisTimestamp The timestamp of the initial snapshot creation
+ * @param balances The balances to add to the provider
+ * @return The supplied snapshotProvider object
+ */
public static SnapshotProvider mockSnapshotProvider(SnapshotProvider snapshotProvider, int milestoneStartIndex,
Hash genesisHash, long genesisTimestamp, Map balances) {
diff --git a/src/test/java/com/iota/iri/service/snapshot/impl/SnapshotProviderImplTest.java b/src/test/java/com/iota/iri/service/snapshot/impl/SnapshotProviderImplTest.java
new file mode 100644
index 0000000000..8e51f88648
--- /dev/null
+++ b/src/test/java/com/iota/iri/service/snapshot/impl/SnapshotProviderImplTest.java
@@ -0,0 +1,59 @@
+package com.iota.iri.service.snapshot.impl;
+
+import static org.junit.Assert.*;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import com.iota.iri.conf.ConfigFactory;
+import com.iota.iri.conf.IotaConfig;
+import com.iota.iri.model.Hash;
+import com.iota.iri.service.snapshot.SnapshotException;
+import com.iota.iri.service.spentaddresses.SpentAddressesException;
+
+public class SnapshotProviderImplTest {
+
+ private SnapshotProviderImpl provider;
+
+ private SnapshotImpl cachedBuildinSnapshot;
+
+ @Before
+ public void setUp(){
+ provider = new SnapshotProviderImpl();
+
+ // When running multiple tests, the static cached snapshot breaks this test
+ cachedBuildinSnapshot = SnapshotProviderImpl.builtinSnapshot;
+ SnapshotProviderImpl.builtinSnapshot = null;
+ }
+
+ @After
+ public void tearDown(){
+ provider.shutdown();
+
+ // Set back the cached snapshot for tests after us who might use it
+ SnapshotProviderImpl.builtinSnapshot = cachedBuildinSnapshot;
+ }
+
+ @Test
+ public void testGetLatestSnapshot() throws SnapshotException, SpentAddressesException {
+ IotaConfig iotaConfig = ConfigFactory.createIotaConfig(true);
+ provider.init(iotaConfig);
+
+ // If we run this on its own, it correctly takes the testnet milestone
+ // However, running it with all tests makes it load the last global snapshot contained in the jar
+ assertEquals("Initial snapshot index should be the same as the milestone start index",
+ iotaConfig.getMilestoneStartIndex(), provider.getInitialSnapshot().getIndex());
+
+ assertEquals("Initial snapshot timestamp should be the same as last snapshot time",
+ iotaConfig.getSnapshotTime(), provider.getInitialSnapshot().getInitialTimestamp());
+
+ assertEquals("Initial snapshot hash should be the genisis transaction",
+ Hash.NULL_HASH, provider.getInitialSnapshot().getHash());
+
+ assertEquals("Initial provider snapshot should be equal to the latest snapshot",
+ provider.getInitialSnapshot(), provider.getLatestSnapshot());
+
+ assertTrue("Initial snapshot should have a filled map of addresses", provider.getInitialSnapshot().getBalances().size() > 0);
+ assertTrue("Initial snapshot supply should be equal to all supply", provider.getInitialSnapshot().hasCorrectSupply());
+ }
+}
diff --git a/src/test/java/com/iota/iri/service/snapshot/impl/SnapshotStateDiffImplTest.java b/src/test/java/com/iota/iri/service/snapshot/impl/SnapshotStateDiffImplTest.java
new file mode 100644
index 0000000000..6b2726b99e
--- /dev/null
+++ b/src/test/java/com/iota/iri/service/snapshot/impl/SnapshotStateDiffImplTest.java
@@ -0,0 +1,49 @@
+package com.iota.iri.service.snapshot.impl;
+
+import static org.junit.Assert.*;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.junit.Test;
+
+import com.iota.iri.TransactionTestUtils;
+import com.iota.iri.model.Hash;
+
+public class SnapshotStateDiffImplTest {
+
+ private static final Hash A = TransactionTestUtils.getTransactionHash();
+ private static final Hash B = TransactionTestUtils.getTransactionHash();
+ private static final Hash C = TransactionTestUtils.getTransactionHash();
+
+ @Test
+ public void getBalanceChanges() {
+ SnapshotStateDiffImpl stateDiff = new SnapshotStateDiffImpl(Collections.EMPTY_MAP);
+ Map change = stateDiff.getBalanceChanges();
+ change.put(A, 1l);
+
+ assertNotEquals("Changes to the statediff balance changes shouldnt reflect on the original state",
+ stateDiff.getBalanceChanges().size(), change.size());
+ }
+
+ @Test
+ public void isConsistent() {
+ SnapshotStateDiffImpl stateDiff = new SnapshotStateDiffImpl(new HashMap(){{
+ put(A, 1l);
+ put(B, 5l);
+ put(C, -6l);
+ }});
+ assertTrue("Sum of diffs should be 0", stateDiff.isConsistent());
+
+ stateDiff = new SnapshotStateDiffImpl(Collections.EMPTY_MAP);
+ assertTrue("Empty diff should be consisntent as sum is 0", stateDiff.isConsistent());
+
+ stateDiff = new SnapshotStateDiffImpl(new HashMap(){{
+ put(A, 1l);
+ put(B, 5l);
+ }});
+
+ assertFalse("Diff sum not 0 shouldnt be consistent", stateDiff.isConsistent());
+ }
+}
diff --git a/src/test/java/com/iota/iri/service/snapshot/impl/SnapshotStateImplTest.java b/src/test/java/com/iota/iri/service/snapshot/impl/SnapshotStateImplTest.java
new file mode 100644
index 0000000000..83f5bef544
--- /dev/null
+++ b/src/test/java/com/iota/iri/service/snapshot/impl/SnapshotStateImplTest.java
@@ -0,0 +1,135 @@
+package com.iota.iri.service.snapshot.impl;
+
+import static org.junit.Assert.*;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.iota.iri.TransactionTestUtils;
+import com.iota.iri.controllers.TransactionViewModel;
+import com.iota.iri.model.Hash;
+import com.iota.iri.service.snapshot.SnapshotException;
+import com.iota.iri.service.snapshot.SnapshotState;
+import com.iota.iri.service.snapshot.SnapshotStateDiff;
+
+public class SnapshotStateImplTest {
+
+ private static final Hash A = TransactionTestUtils.getTransactionHash();
+ private static final Hash B = TransactionTestUtils.getTransactionHash();
+
+ private static Map map = new HashMap(){{
+ put(Hash.NULL_HASH, TransactionViewModel.SUPPLY - 10);
+ put(A, 10l);
+ }};
+
+ private static Map inconsistentMap = new HashMap(){{
+ put(Hash.NULL_HASH, 5l);
+ put(A, -10l);
+ }};
+
+ private SnapshotStateImpl state;
+ private SnapshotStateImpl balanceState;
+
+ @Before
+ public void setUp() throws Exception {
+ state = new SnapshotStateImpl(new HashMap<>());
+ balanceState = new SnapshotStateImpl(map);
+ }
+
+ @Test
+ public void testGetBalance() {
+ assertNull("Unknown address should return null", balanceState.getBalance(null));
+
+ long balance = balanceState.getBalance(Hash.NULL_HASH);
+ assertEquals("Balance should be total - 10", TransactionViewModel.SUPPLY - 10l, balance);
+
+ balance = balanceState.getBalance(A);
+ assertEquals("Balance should be 10", 10l, balance);
+ }
+
+ @Test
+ public void testGetBalances() {
+ assertEquals("State should not have balances", new HashMap<>(), state.getBalances());
+ assertEquals("State should have the balances it was created with", map, balanceState.getBalances());
+ }
+
+ @Test
+ public void testIsConsistent() {
+ assertTrue("Empty balance should be consistent", state.isConsistent());
+ assertTrue("No negative balances should be consistent", balanceState.isConsistent());
+
+ SnapshotStateImpl inconsistentState = new SnapshotStateImpl(inconsistentMap);
+ assertFalse("Negative balances should not be consistent", inconsistentState.isConsistent());
+ }
+
+ @Test
+ public void testHasCorrectSupply() {
+ assertFalse("Empty state should not have correct supply", state.hasCorrectSupply());
+ assertTrue("State with total supply should have correct supply", balanceState.hasCorrectSupply());
+
+ SnapshotStateImpl inconsistentState = new SnapshotStateImpl(inconsistentMap);
+ assertFalse("Inconsistent state without full supply should be incorrect", inconsistentState.hasCorrectSupply());
+
+ Map map = new HashMap<>();
+ map.put(Hash.NULL_HASH, TransactionViewModel.SUPPLY - 10);
+ map.put(A, -10l);
+ map.put(B, 20l);
+ assertFalse("Inconsistent state with full supply should be correct", inconsistentState.hasCorrectSupply());
+ }
+
+ @Test
+ public void testUpdate() {
+ assertNotEquals("States with different balances should not be equal", state, balanceState);
+ state.update(balanceState);
+
+ assertEquals("Updating a state with another state should make them equal", state, balanceState);
+ }
+
+ @Test
+ public void testApplyStateDiff() throws SnapshotException {
+ Map map = new HashMap<>();
+ map.put(Hash.NULL_HASH, 5l);
+ map.put(A, -5l);
+
+ SnapshotStateDiff diff = new SnapshotStateDiffImpl(map);
+ state.applyStateDiff(diff);
+
+ long balance = state.getBalance(Hash.NULL_HASH);
+ assertEquals("Applying state to an empty state should have 5 for genesis", 5l, balance);
+
+ balance = state.getBalance(A);
+ assertEquals("Applying state to an empty state should have -5 for A", -5l, balance);
+ }
+
+ @Test(expected = SnapshotException.class)
+ public void testApplyStateDiffThrowsException() throws SnapshotException {
+ SnapshotStateDiff diff = new SnapshotStateDiffImpl(inconsistentMap);
+ state.applyStateDiff(diff);
+
+ fail("Applying an inconsistent state should throw an exception");
+ }
+
+ @Test
+ public void testPatchedState() {
+ SnapshotStateDiff diff = new SnapshotStateDiffImpl(map);
+ SnapshotState patchedState = state.patchedState(diff);
+
+ assertEquals("Patching an empty state with a map should equal to creation with that map", patchedState, balanceState);
+
+ Map map = new HashMap<>();
+ map.put(Hash.NULL_HASH, 5l);
+ map.put(A, -5l);
+
+ diff = new SnapshotStateDiffImpl(map);
+ patchedState = balanceState.patchedState(diff);
+
+ long balance = patchedState.getBalance(Hash.NULL_HASH);
+ assertEquals("5 should have been added to genesis", TransactionViewModel.SUPPLY - 5l, balance);
+
+ balance = patchedState.getBalance(A);
+ assertEquals("5 should have been removed from A", 5, balance);
+ }
+}