From f4812bab9d9d316c7fe22285fb576a9d00bc7831 Mon Sep 17 00:00:00 2001 From: spangin <> Date: Sat, 15 Jun 2019 21:43:16 +0300 Subject: [PATCH 1/6] TransactionRequesterWorkerImpl refactoring --- .../network/TransactionRequesterWorker.java | 2 +- .../impl/TransactionRequesterWorkerImpl.java | 45 +++++++++++++------ 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/src/main/java/net/helix/hlx/network/TransactionRequesterWorker.java b/src/main/java/net/helix/hlx/network/TransactionRequesterWorker.java index a5aca7aa..67a809dc 100644 --- a/src/main/java/net/helix/hlx/network/TransactionRequesterWorker.java +++ b/src/main/java/net/helix/hlx/network/TransactionRequesterWorker.java @@ -11,7 +11,7 @@ public interface TransactionRequesterWorker { /** * Works through the request queue by sending a request alongside a random tip to each of our neighbors.
*/ - void processRequestQueue(); + boolean processRequestQueue(); /** * Starts the background worker that automatically calls {@link #processRequestQueue()} periodically to process the diff --git a/src/main/java/net/helix/hlx/network/impl/TransactionRequesterWorkerImpl.java b/src/main/java/net/helix/hlx/network/impl/TransactionRequesterWorkerImpl.java index 37f095d6..4eee5298 100644 --- a/src/main/java/net/helix/hlx/network/impl/TransactionRequesterWorkerImpl.java +++ b/src/main/java/net/helix/hlx/network/impl/TransactionRequesterWorkerImpl.java @@ -31,7 +31,7 @@ public class TransactionRequesterWorkerImpl implements TransactionRequesterWorke /** * The minimum amount of transactions in the request queue that are required for the worker to trigger.
*/ - private static final int REQUESTER_THREAD_ACTIVATION_THRESHOLD = 50; + public static final int REQUESTER_THREAD_ACTIVATION_THRESHOLD = 50; /** * The time (in milliseconds) that the worker waits between its iterations.
@@ -106,24 +106,42 @@ public TransactionRequesterWorkerImpl init(Tangle tangle, TransactionRequester t * traffic like transactions that get relayed by our node.
*/ @Override - public void processRequestQueue() { + public boolean processRequestQueue() { try { - if (transactionRequester.numberOfTransactionsToRequest() >= REQUESTER_THREAD_ACTIVATION_THRESHOLD) { + if (isActive()) { TransactionViewModel transaction = getTransactionToSendWithRequest(); - if (transaction != null && transaction.getType() != TransactionViewModel.PREFILLED_SLOT) { - for (Neighbor neighbor : node.getNeighbors()) { - try { - // automatically adds the hash of a requested transaction when sending a packet - node.sendPacket(transaction, neighbor); - } catch (Exception e) { - log.error("unexpected error while sending request to neighbour", e); - } - } + if (isValidTransaction(transaction)) { + sendToNodes(transaction); + return true; } } } catch (Exception e) { log.error("unexpected error while processing the request queue", e); } + return false; + } + + private void sendToNodes(TransactionViewModel transaction) { + for (Neighbor neighbor : node.getNeighbors()) { + try { + // automatically adds the hash of a requested transaction when sending a packet + node.sendPacket(transaction, neighbor); + } catch (Exception e) { + log.error("unexpected error while sending request to neighbour", e); + } + } + } + + //@VisibleForTesting + boolean isActive() { + return transactionRequester.numberOfTransactionsToRequest() >= REQUESTER_THREAD_ACTIVATION_THRESHOLD; + } + + //@VisibleForTesting + boolean isValidTransaction(TransactionViewModel transaction) { + return transaction != null && ( + transaction.getType() != TransactionViewModel.PREFILLED_SLOT + || transaction.getHash().equals(Hash.NULL_HASH)); } @Override @@ -146,7 +164,8 @@ public void shutdown() { * @return a random tip * @throws Exception if anything unexpected happens while trying to retrieve the random tip. */ - private TransactionViewModel getTransactionToSendWithRequest() throws Exception { + //@VisibleForTesting + TransactionViewModel getTransactionToSendWithRequest() throws Exception { Hash tip = tipsViewModel.getRandomSolidTipHash(); if (tip == null) { tip = tipsViewModel.getRandomNonSolidTipHash(); From c23ee53ee9d76889f25d19402022a895344417fe Mon Sep 17 00:00:00 2001 From: spangin <> Date: Sat, 15 Jun 2019 21:47:46 +0300 Subject: [PATCH 2/6] javadoc was updated --- .../java/net/helix/hlx/network/TransactionRequesterWorker.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/net/helix/hlx/network/TransactionRequesterWorker.java b/src/main/java/net/helix/hlx/network/TransactionRequesterWorker.java index 67a809dc..6462c353 100644 --- a/src/main/java/net/helix/hlx/network/TransactionRequesterWorker.java +++ b/src/main/java/net/helix/hlx/network/TransactionRequesterWorker.java @@ -10,6 +10,8 @@ public interface TransactionRequesterWorker { /** * Works through the request queue by sending a request alongside a random tip to each of our neighbors.
+ * + * @return true when we have send the request to our neighbors, otherwise false */ boolean processRequestQueue(); From 414e590eaa224846b724d0c577132f7ef6aa872a Mon Sep 17 00:00:00 2001 From: spangin <> Date: Sat, 15 Jun 2019 21:58:20 +0300 Subject: [PATCH 3/6] new util functions were added --- .../net/helix/hlx/TransactionTestUtils.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/test/java/net/helix/hlx/TransactionTestUtils.java b/src/test/java/net/helix/hlx/TransactionTestUtils.java index aa2c3bdf..f2542dff 100644 --- a/src/test/java/net/helix/hlx/TransactionTestUtils.java +++ b/src/test/java/net/helix/hlx/TransactionTestUtils.java @@ -2,16 +2,49 @@ import java.util.Random; import net.helix.hlx.model.Hash; +import net.helix.hlx.controllers.TransactionViewModel; +import net.helix.hlx.model.persistables.Transaction; public class TransactionTestUtils { private static final Random RND = new Random(); + + /** + * Generates a transaction hash. + * + * @return The transaction hash + */ public static Hash getTransactionHash() { byte[] bytes = new byte[Hash.SIZE_IN_BYTES]; RND.nextBytes(bytes); return net.helix.hlx.model.HashFactory.TRANSACTION.create(bytes); } + /** + * Generates a transaction. + * + * @return The transaction + */ + public static Transaction getTransaction() { + byte[] bytes = new byte[TransactionViewModel.SIZE]; + RND.nextBytes(bytes); + Transaction tx = new Transaction(); + tx.read(bytes); + return tx; + } + + /** + * Generates a transaction with only 0s. + * + * @return The transaction + */ + public static Transaction get0Transaction() { + byte[] bytes = new byte[TransactionViewModel.SIZE]; + Transaction tx = new Transaction(); + tx.read(bytes); + return tx; + } + } From 0789f073d22dea08cf6bbd5053330be3ccf9b0e6 Mon Sep 17 00:00:00 2001 From: spangin <> Date: Sat, 15 Jun 2019 22:50:07 +0300 Subject: [PATCH 4/6] building a transaction from bytes was added --- .../net/helix/hlx/TransactionTestUtils.java | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/test/java/net/helix/hlx/TransactionTestUtils.java b/src/test/java/net/helix/hlx/TransactionTestUtils.java index f2542dff..6e5b3351 100644 --- a/src/test/java/net/helix/hlx/TransactionTestUtils.java +++ b/src/test/java/net/helix/hlx/TransactionTestUtils.java @@ -30,9 +30,7 @@ public static Hash getTransactionHash() { public static Transaction getTransaction() { byte[] bytes = new byte[TransactionViewModel.SIZE]; RND.nextBytes(bytes); - Transaction tx = new Transaction(); - tx.read(bytes); - return tx; + return buildTransaction(bytes); } /** @@ -42,9 +40,21 @@ public static Transaction getTransaction() { */ public static Transaction get0Transaction() { byte[] bytes = new byte[TransactionViewModel.SIZE]; - Transaction tx = new Transaction(); - tx.read(bytes); - return tx; + return buildTransaction(bytes); + } + + /** + * Builds a transaction from the bytes. + * Make sure the bytes are in the correct order + * + * @param bytes The bytes to build the transaction + * @return The created transaction + */ + public static Transaction buildTransaction(byte[] bytes) { + Transaction t = new Transaction(); + t.read(bytes); + t.readMetadata(bytes); + return t; } } From 2f8124729ef024529813dd00cb5167df06e15a65 Mon Sep 17 00:00:00 2001 From: spangin <> Date: Sun, 16 Jun 2019 13:27:39 +0300 Subject: [PATCH 5/6] TangleMockUtils was added --- .../java/net/helix/hlx/TangleMockUtils.java | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 src/test/java/net/helix/hlx/TangleMockUtils.java diff --git a/src/test/java/net/helix/hlx/TangleMockUtils.java b/src/test/java/net/helix/hlx/TangleMockUtils.java new file mode 100644 index 00000000..fe6d5855 --- /dev/null +++ b/src/test/java/net/helix/hlx/TangleMockUtils.java @@ -0,0 +1,50 @@ +package net.helix.hlx; + +import net.helix.hlx.controllers.TransactionViewModel; +import net.helix.hlx.model.Hash; +import net.helix.hlx.model.persistables.Transaction; +import net.helix.hlx.storage.Tangle; +import net.helix.hlx.utils.Pair; + +import org.mockito.Mockito; + + +public class TangleMockUtils { + + /** + * Creates an empty transaction, which is marked filled and parsed. + * This transaction is returned when the hash is asked to load in the tangle object + * + * @param tangle mocked tangle object that shall retrieve a milestone object when being queried for it + * @param hash transaction hash + * @return The newly created (empty) transaction + */ + public static Transaction mockTransaction(Tangle tangle, Hash hash) { + Transaction transaction = new Transaction(); + transaction.bytes = new byte[0]; + transaction.type = TransactionViewModel.FILLED_SLOT; + transaction.parsed = true; + + return mockTransaction(tangle, hash, transaction); + } + + /** + * Mocks the tangle object by checking for the hash and returning the transaction. + * + * @param tangle mocked tangle object that shall retrieve a milestone object when being queried for it + * @param hash transaction hash + * @param transaction the transaction we send back + * @return The transaction + */ + public static Transaction mockTransaction(Tangle tangle, Hash hash, Transaction transaction) { + try { + Mockito.when(tangle.load(Transaction.class, hash)).thenReturn(transaction); + Mockito.when(tangle.getLatest(Transaction.class, Hash.class)).thenReturn(new Pair<>(hash, transaction)); + } catch (Exception e) { + // the exception can not be raised since we mock + } + + return transaction; + } + +} From 1a76844e97ef9c369ee37eb0220dd08f86656ef1 Mon Sep 17 00:00:00 2001 From: spangin <> Date: Sun, 16 Jun 2019 13:29:06 +0300 Subject: [PATCH 6/6] test for TransactionRequesterWorkerImpl --- .../TransactionRequesterWorkerImplTest.java | 148 ++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 src/test/java/net/helix/hlx/network/impl/TransactionRequesterWorkerImplTest.java diff --git a/src/test/java/net/helix/hlx/network/impl/TransactionRequesterWorkerImplTest.java b/src/test/java/net/helix/hlx/network/impl/TransactionRequesterWorkerImplTest.java new file mode 100644 index 00000000..b2b70082 --- /dev/null +++ b/src/test/java/net/helix/hlx/network/impl/TransactionRequesterWorkerImplTest.java @@ -0,0 +1,148 @@ +package net.helix.hlx.network.impl; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertFalse; + +import org.mockito.Answers; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import static org.mockito.Mockito.when; + +import net.helix.hlx.controllers.TipsViewModel; +import net.helix.hlx.controllers.TransactionViewModel; +import net.helix.hlx.model.Hash; +import net.helix.hlx.model.persistables.Transaction; +import net.helix.hlx.network.Node; +import net.helix.hlx.network.TransactionRequester; +import net.helix.hlx.service.snapshot.SnapshotProvider; +import net.helix.hlx.storage.Tangle; + +import net.helix.hlx.TangleMockUtils; +import static net.helix.hlx.TransactionTestUtils.getTransaction; +import static net.helix.hlx.TransactionTestUtils.get0Transaction; +import static net.helix.hlx.TransactionTestUtils.buildTransaction; +import static net.helix.hlx.TransactionTestUtils.getTransactionHash; + + +public class TransactionRequesterWorkerImplTest { + + //Good + private static final TransactionViewModel TVMRandomNull = new TransactionViewModel( + getTransaction(), Hash.NULL_HASH); + private static final TransactionViewModel TVMRandomNotNull = new TransactionViewModel( + getTransaction(), getTransactionHash()); + private static final TransactionViewModel TVMAll0Null = new TransactionViewModel( + get0Transaction(), Hash.NULL_HASH); + private static final TransactionViewModel TVMAll0NotNull = new TransactionViewModel( + get0Transaction(), getTransactionHash()); + + //Bad + private static final TransactionViewModel TVMNullNull = new TransactionViewModel((Transaction)null, Hash.NULL_HASH); + + @Rule + public MockitoRule mockitoRule = MockitoJUnit.rule(); + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private static SnapshotProvider snapshotProvider; + + private static TransactionRequester requester; + private static TransactionRequesterWorkerImpl worker; + + @Mock + private Tangle tangle; + + @Mock + private Node node; + + @Mock + private TipsViewModel tipsVM; + + @Before + public void before() { + requester = new TransactionRequester(tangle, snapshotProvider); + + worker = new TransactionRequesterWorkerImpl(); + worker.init(tangle, requester, tipsVM, node); + } + + @After + public void tearDown() { + worker.shutdown(); + } + + @Test + public void workerActive() throws Exception { + assertFalse("Empty worker should not be active", worker.isActive()); + + fillRequester(); + + assertTrue("Worker should be active when it requester is over threshold", worker.isActive()); + } + + @Test + public void processRequestQueueTest() throws Exception { + //getTransactionToSendWithRequest starts reading from solid tips, so mock data from that call + when(tipsVM.getRandomSolidTipHash()).thenReturn( + TVMRandomNull.getHash(), + TVMRandomNotNull.getHash(), + TVMAll0Null.getHash(), + TVMAll0NotNull.getHash(), + TVMNullNull.getHash(), + null); + + assertFalse("Unfilled queue shouldnt process", worker.processRequestQueue()); + + //Requester never goes down since nodes don't really request + fillRequester(); + + TangleMockUtils.mockTransaction(tangle, TVMRandomNull.getHash(), buildTransaction(TVMRandomNull.getBytes())); + assertTrue("Null transaction hash should be processed", worker.processRequestQueue()); + + TangleMockUtils.mockTransaction(tangle, TVMRandomNotNull.getHash(), buildTransaction(TVMRandomNotNull.getBytes())); + assertTrue("Not null transaction hash should be processed", worker.processRequestQueue()); + + TangleMockUtils.mockTransaction(tangle, TVMAll0Null.getHash(), buildTransaction(TVMAll0Null.getBytes())); + assertTrue("Null transaction hash should be processed", worker.processRequestQueue()); + + TangleMockUtils.mockTransaction(tangle, TVMAll0NotNull.getHash(), buildTransaction(TVMAll0NotNull.getBytes())); + assertTrue("All 9s transaction should be processed", worker.processRequestQueue()); + + // Null gets loaded as all 0, so type is 0 -> Filled + TangleMockUtils.mockTransaction(tangle, TVMNullNull.getHash(), null); + assertTrue("0 transaction should be processed", worker.processRequestQueue()); + + // null -> NULL_HASH -> gets loaded as all 0 -> filled + assertTrue("Null transaction should be processed", worker.processRequestQueue()); + } + + @Test + public void validTipToAddTest() throws Exception { + assertTrue("Null transaction hash should always be accepted", worker.isValidTransaction(TVMRandomNull)); + assertTrue("Not null transaction hash should always be accepted", worker.isValidTransaction(TVMRandomNotNull)); + assertTrue("Null transaction hash should always be accepted", worker.isValidTransaction(TVMAll0Null)); + assertTrue("All 9s transaction should be accepted", worker.isValidTransaction(TVMAll0NotNull)); + + // Null gets loaded as all 0, so type is 0 -> Filled + assertTrue("0 transaction should be accepted", worker.isValidTransaction(TVMNullNull)); + + assertFalse("Null transaction should not be accepted", worker.isValidTransaction(null)); + } + + private void fillRequester() throws Exception { + for (int i=0; i< TransactionRequesterWorkerImpl.REQUESTER_THREAD_ACTIVATION_THRESHOLD; i++) { + addRequest(); + } + } + + private void addRequest() throws Exception { + Hash randomHash = getTransactionHash(); + TangleMockUtils.mockTransaction(tangle, randomHash); + requester.requestTransaction(randomHash, false); + } + +}