diff --git a/python-regression/tests/features/steps/api_test_steps.py b/python-regression/tests/features/steps/api_test_steps.py
index 94f9bff418..9afdc7a8d5 100644
--- a/python-regression/tests/features/steps/api_test_steps.py
+++ b/python-regression/tests/features/steps/api_test_steps.py
@@ -1,14 +1,13 @@
from aloe import *
from util import static_vals
+from util import logger as log
from util.test_logic import api_test_logic as api_utils
from util.threading_logic import pool_logic as pool
from util.neighbor_logic import neighbor_logic as neighbors
from util.response_logic import response_handling as responses
from time import sleep, time
-import logging
-logging.basicConfig(level=logging.INFO)
-logger = logging.getLogger(__name__)
+logger = log.getLogger(__name__)
testAddress = static_vals.TEST_ADDRESS
diff --git a/python-regression/tests/features/steps/local_snapshots_steps.py b/python-regression/tests/features/steps/local_snapshots_steps.py
index 831b177c5e..1026598605 100644
--- a/python-regression/tests/features/steps/local_snapshots_steps.py
+++ b/python-regression/tests/features/steps/local_snapshots_steps.py
@@ -1,9 +1,8 @@
from aloe import step
from util.test_logic import api_test_logic as api_utils
+from util import logger as log
-import logging
-logging.basicConfig(level=logging.INFO)
-logger = logging.getLogger(__name__)
+logger = log.getLogger(__name__)
@step(r'A local snapshot was taken on "([^"]+)" at index (\d+)')
diff --git a/python-regression/tests/features/steps/response_handling_steps.py b/python-regression/tests/features/steps/response_handling_steps.py
index 7e1817a7ea..320cdb645d 100644
--- a/python-regression/tests/features/steps/response_handling_steps.py
+++ b/python-regression/tests/features/steps/response_handling_steps.py
@@ -1,11 +1,10 @@
-import logging
from aloe import world, step
-from util.response_logic import response_handling as response_handling
from util.test_logic import api_test_logic as api_utils
from util.test_logic import value_fetch_logic
+from util.response_logic import response_handling as response_handling
+from util import logger as log
-logging.basicConfig(level=logging.INFO)
-logger = logging.getLogger(__name__)
+logger = log.getLogger(__name__)
world.test_vars = {}
diff --git a/python-regression/tests/features/steps/transaction_steps.py b/python-regression/tests/features/steps/transaction_steps.py
index bb485b5668..4be9648fa5 100644
--- a/python-regression/tests/features/steps/transaction_steps.py
+++ b/python-regression/tests/features/steps/transaction_steps.py
@@ -1,15 +1,13 @@
from aloe import world, step
from iota import Transaction
from util import static_vals as static
+from util import logger as log
from util.test_logic import api_test_logic as api_utils
from util.transaction_bundle_logic import transaction_logic as transactions
from util.milestone_logic import milestones
from time import sleep
-import logging
-logging.basicConfig(level=logging.INFO)
-logger = logging.getLogger(__name__)
-
+logger = log.getLogger(__name__)
@step(r'a transaction is generated and attached on "([^"]+)" with:')
def generate_transaction_and_attach(step, node):
@@ -204,7 +202,8 @@ def wait_for_update(index, api):
if node_info['latestSolidSubtangleMilestoneIndex'] == index:
updated = True
break
- i += 1;
+ i += 1
sleep(1)
+ logger.info("Waiting... {}".format(i))
assert updated is True, "The node was unable to update to index {}".format(index)
diff --git a/python-regression/util/logger.py b/python-regression/util/logger.py
new file mode 100644
index 0000000000..d9e758795b
--- /dev/null
+++ b/python-regression/util/logger.py
@@ -0,0 +1,5 @@
+import logging
+logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s: %(message)s')
+
+def getLogger(name):
+ return logging.getLogger(name)
\ No newline at end of file
diff --git a/python-regression/util/milestone_logic/milestones.py b/python-regression/util/milestone_logic/milestones.py
index 4eb197ef52..760cd0438b 100644
--- a/python-regression/util/milestone_logic/milestones.py
+++ b/python-regression/util/milestone_logic/milestones.py
@@ -1,11 +1,9 @@
from iota import ProposedTransaction, ProposedBundle, Tag, Address, Transaction
from util import conversion as converter
+from util import logger as log
from util.transaction_bundle_logic import bundle_logic
-import logging
-logging.basicConfig(level=logging.INFO)
-logger = logging.getLogger(__name__)
-
+logger = log.getLogger(__name__)
def issue_milestone(address, api, index, *reference_transaction):
txn1 = ProposedTransaction(
diff --git a/python-regression/util/neighbor_logic/neighbor_logic.py b/python-regression/util/neighbor_logic/neighbor_logic.py
index c8ba9c5759..34c5ebcf1d 100644
--- a/python-regression/util/neighbor_logic/neighbor_logic.py
+++ b/python-regression/util/neighbor_logic/neighbor_logic.py
@@ -1,6 +1,5 @@
-import logging
-logging.basicConfig(level=logging.INFO)
-logger = logging.getLogger(__name__)
+from util import logger as log
+logger = log.getLogger(__name__)
def check_if_neighbors(api, neighbors, expected_neighbor):
diff --git a/python-regression/util/response_logic/response_handling.py b/python-regression/util/response_logic/response_handling.py
index ada6a98334..b485f33c83 100644
--- a/python-regression/util/response_logic/response_handling.py
+++ b/python-regression/util/response_logic/response_handling.py
@@ -1,8 +1,7 @@
-import logging
from util.threading_logic import pool_logic as pool
+from util import logger as log
-logging.basicConfig(level=logging.INFO)
-logger = logging.getLogger(__name__)
+logger = log.getLogger(__name__)
def find_in_response(key, response):
diff --git a/python-regression/util/test_logic/api_test_logic.py b/python-regression/util/test_logic/api_test_logic.py
index 95019c053b..f6fe3f858d 100644
--- a/python-regression/util/test_logic/api_test_logic.py
+++ b/python-regression/util/test_logic/api_test_logic.py
@@ -1,13 +1,12 @@
import json
-import logging
import urllib3
from aloe import world
from iota import Iota, Address, Tag, TryteString
from . import value_fetch_logic as value_fetch
+from util import logger as log
-logging.basicConfig(level=logging.INFO)
-logger = logging.getLogger(__name__)
+logger = log.getLogger(__name__)
def prepare_api_call(node_name, **kwargs):
diff --git a/python-regression/util/threading_logic/pool_logic.py b/python-regression/util/threading_logic/pool_logic.py
index 97a6171597..6722f12be2 100644
--- a/python-regression/util/threading_logic/pool_logic.py
+++ b/python-regression/util/threading_logic/pool_logic.py
@@ -1,7 +1,6 @@
from multiprocessing.dummy import Pool
-import logging
-logging.basicConfig(level=logging.DEBUG)
-logger = logging.getLogger(__name__)
+from util import logger as log
+logger = log.getLogger(__name__)
def start_pool(function, iterations, args):
diff --git a/python-regression/util/transaction_bundle_logic/transaction_logic.py b/python-regression/util/transaction_bundle_logic/transaction_logic.py
index e82b49e533..7b100aac11 100644
--- a/python-regression/util/transaction_bundle_logic/transaction_logic.py
+++ b/python-regression/util/transaction_bundle_logic/transaction_logic.py
@@ -2,10 +2,9 @@
from util import static_vals as static
from util.test_logic import api_test_logic as api_utils
from util.test_logic import value_fetch_logic as value_fetch
-import logging
+from util import logger as log
-logging.basicConfig(level=logging.INFO)
-logger = logging.getLogger(__name__)
+logger = log.getLogger(__name__)
def create_transaction_bundle(address, tag, value):
diff --git a/src/main/java/com/iota/iri/Iota.java b/src/main/java/com/iota/iri/Iota.java
index c901fe8fff..420cc51c71 100644
--- a/src/main/java/com/iota/iri/Iota.java
+++ b/src/main/java/com/iota/iri/Iota.java
@@ -22,6 +22,8 @@
import com.iota.iri.service.transactionpruning.DepthPruningCondition;
import com.iota.iri.service.transactionpruning.SizePruningCondition;
import com.iota.iri.service.transactionpruning.TransactionPruner;
+import com.iota.iri.service.validation.TransactionSolidifier;
+import com.iota.iri.service.validation.TransactionValidator;
import com.iota.iri.storage.*;
import com.iota.iri.storage.rocksDB.RocksDBPersistenceProvider;
import com.iota.iri.utils.Pair;
@@ -95,6 +97,8 @@ public class Iota {
public final MilestoneSolidifier milestoneSolidifier;
+ public final TransactionSolidifier transactionSolidifier;
+
public final BundleValidator bundleValidator;
public final Tangle tangle;
@@ -126,7 +130,7 @@ public Iota(IotaConfig configuration, SpentAddressesProvider spentAddressesProvi
TransactionRequester transactionRequester, NeighborRouter neighborRouter,
TransactionProcessingPipeline transactionProcessingPipeline, TipsRequester tipsRequester,
TipsViewModel tipsViewModel, TipSelector tipsSelector, LocalSnapshotsPersistenceProvider localSnapshotsDb,
- CacheManager cacheManager) {
+ CacheManager cacheManager, TransactionSolidifier transactionSolidifier) {
this.configuration = configuration;
this.ledgerService = ledgerService;
@@ -144,9 +148,9 @@ public Iota(IotaConfig configuration, SpentAddressesProvider spentAddressesProvi
this.neighborRouter = neighborRouter;
this.txPipeline = transactionProcessingPipeline;
this.tipsRequester = tipsRequester;
+ this.transactionSolidifier = transactionSolidifier;
this.localSnapshotsDb = localSnapshotsDb;
-
// legacy classes
this.bundleValidator = bundleValidator;
this.tangle = tangle;
@@ -199,8 +203,6 @@ public void init() throws Exception {
tangle.clearMetadata(com.iota.iri.model.persistables.Transaction.class);
}
- transactionValidator.init();
-
txPipeline.start();
neighborRouter.start();
tipsRequester.start();
@@ -209,6 +211,7 @@ public void init() throws Exception {
latestSolidMilestoneTracker.start();
seenMilestonesRetriever.start();
milestoneSolidifier.start();
+ transactionSolidifier.start();
if (localSnapshotManager != null) {
localSnapshotManager.addSnapshotCondition(new SnapshotDepthCondition(configuration, snapshotProvider));
@@ -255,6 +258,7 @@ private void rescanDb() throws Exception {
public void shutdown() throws Exception {
// shutdown in reverse starting order (to not break any dependencies)
milestoneSolidifier.shutdown();
+ transactionSolidifier.shutdown();
seenMilestonesRetriever.shutdown();
latestSolidMilestoneTracker.shutdown();
latestMilestoneTracker.shutdown();
@@ -269,7 +273,6 @@ public void shutdown() throws Exception {
tipsRequester.shutdown();
txPipeline.shutdown();
neighborRouter.shutdown();
- transactionValidator.shutdown();
localSnapshotsDb.shutdown();
tangle.shutdown();
diff --git a/src/main/java/com/iota/iri/MainInjectionConfiguration.java b/src/main/java/com/iota/iri/MainInjectionConfiguration.java
index e8347fe6f1..221256930d 100644
--- a/src/main/java/com/iota/iri/MainInjectionConfiguration.java
+++ b/src/main/java/com/iota/iri/MainInjectionConfiguration.java
@@ -27,6 +27,9 @@
import com.iota.iri.service.tipselection.impl.*;
import com.iota.iri.service.transactionpruning.TransactionPruner;
import com.iota.iri.service.transactionpruning.async.AsyncTransactionPruner;
+import com.iota.iri.service.validation.TransactionSolidifier;
+import com.iota.iri.service.validation.TransactionValidator;
+import com.iota.iri.service.validation.impl.TransactionSolidifierImpl;
import com.iota.iri.storage.LocalSnapshotsPersistenceProvider;
import com.iota.iri.storage.Tangle;
import com.iota.iri.storage.rocksDB.RocksDBPersistenceProvider;
@@ -114,8 +117,8 @@ SeenMilestonesRetriever provideSeenMilestonesRetriever(Tangle tangle, SnapshotPr
@Singleton
@Provides
- MilestoneSolidifier provideMilestoneSolidifier(SnapshotProvider snapshotProvider, TransactionValidator transactionValidator) {
- return new MilestoneSolidifierImpl(snapshotProvider, transactionValidator);
+ MilestoneSolidifier provideMilestoneSolidifier(SnapshotProvider snapshotProvider, TransactionSolidifier transactionSolidifier) {
+ return new MilestoneSolidifierImpl(snapshotProvider, transactionSolidifier);
}
@Singleton
@@ -136,8 +139,14 @@ LocalSnapshotManager provideLocalSnapshotManager(SnapshotProvider snapshotProvid
@Singleton
@Provides
- TransactionValidator provideTransactionValidator(Tangle tangle, SnapshotProvider snapshotProvider, TipsViewModel tipsViewModel, TransactionRequester transactionRequester) {
- return new TransactionValidator(tangle, snapshotProvider, tipsViewModel, transactionRequester, configuration);
+ TransactionValidator provideTransactionValidator(SnapshotProvider snapshotProvider, TransactionRequester transactionRequester) {
+ return new TransactionValidator(snapshotProvider, transactionRequester, configuration);
+ }
+
+ @Singleton
+ @Provides
+ TransactionSolidifier provideTransactionSolidifier(Tangle tangle, SnapshotProvider snapshotProvider, TransactionRequester transactionRequester, TipsViewModel tipsViewModel){
+ return new TransactionSolidifierImpl(tangle, snapshotProvider, transactionRequester, tipsViewModel);
}
@Singleton
@@ -171,12 +180,12 @@ Iota provideIota(SpentAddressesProvider spentAddressesProvider, SpentAddressesSe
TransactionRequester transactionRequester, NeighborRouter neighborRouter,
TransactionProcessingPipeline transactionProcessingPipeline, TipsRequester tipsRequester,
TipsViewModel tipsViewModel, TipSelector tipsSelector, LocalSnapshotsPersistenceProvider localSnapshotsDb,
- CacheManager cacheManager) {
+ CacheManager cacheManager, TransactionSolidifier transactionSolidifier) {
return new Iota(configuration, spentAddressesProvider, spentAddressesService, snapshotProvider, snapshotService,
localSnapshotManager, milestoneService, latestMilestoneTracker, latestSolidMilestoneTracker,
seenMilestonesRetriever, ledgerService, transactionPruner, milestoneSolidifier, bundleValidator, tangle,
transactionValidator, transactionRequester, neighborRouter, transactionProcessingPipeline,
- tipsRequester, tipsViewModel, tipsSelector, localSnapshotsDb, cacheManager);
+ tipsRequester, tipsViewModel, tipsSelector, localSnapshotsDb, cacheManager, transactionSolidifier);
}
@Singleton
@@ -191,8 +200,8 @@ API provideApi(IXI ixi, TransactionRequester transactionRequester,
SpentAddressesService spentAddressesService, Tangle tangle, BundleValidator bundleValidator,
SnapshotProvider snapshotProvider, LedgerService ledgerService, NeighborRouter neighborRouter, TipSelector tipsSelector,
TipsViewModel tipsViewModel, TransactionValidator transactionValidator,
- LatestMilestoneTracker latestMilestoneTracker, TransactionProcessingPipeline txPipeline) {
- return new API(configuration, ixi, transactionRequester, spentAddressesService, tangle, bundleValidator, snapshotProvider, ledgerService, neighborRouter, tipsSelector, tipsViewModel, transactionValidator, latestMilestoneTracker, txPipeline);
+ LatestMilestoneTracker latestMilestoneTracker, TransactionProcessingPipeline txPipeline, TransactionSolidifier transactionSolidifier) {
+ return new API(configuration, ixi, transactionRequester, spentAddressesService, tangle, bundleValidator, snapshotProvider, ledgerService, neighborRouter, tipsSelector, tipsViewModel, transactionValidator, latestMilestoneTracker, txPipeline, transactionSolidifier);
}
@Singleton
diff --git a/src/main/java/com/iota/iri/TransactionValidator.java b/src/main/java/com/iota/iri/TransactionValidator.java
deleted file mode 100644
index ef51b5c203..0000000000
--- a/src/main/java/com/iota/iri/TransactionValidator.java
+++ /dev/null
@@ -1,465 +0,0 @@
-package com.iota.iri;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.iota.iri.conf.ProtocolConfig;
-import com.iota.iri.controllers.TipsViewModel;
-import com.iota.iri.controllers.TransactionViewModel;
-import com.iota.iri.crypto.Curl;
-import com.iota.iri.crypto.Sponge;
-import com.iota.iri.crypto.SpongeFactory;
-import com.iota.iri.model.Hash;
-import com.iota.iri.model.TransactionHash;
-import com.iota.iri.network.TransactionRequester;
-import com.iota.iri.service.snapshot.SnapshotProvider;
-import com.iota.iri.storage.Tangle;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.*;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import static com.iota.iri.controllers.TransactionViewModel.*;
-
-public class TransactionValidator {
- private static final Logger log = LoggerFactory.getLogger(TransactionValidator.class);
- private static final int TESTNET_MWM_CAP = 13;
- public static final int SOLID_SLEEP_TIME = 500;
-
- private final Tangle tangle;
- private final SnapshotProvider snapshotProvider;
- private final TipsViewModel tipsViewModel;
- private final TransactionRequester transactionRequester;
- private int minWeightMagnitude = 81;
- private static final long MAX_TIMESTAMP_FUTURE = 2L * 60L * 60L;
- private static final long MAX_TIMESTAMP_FUTURE_MS = MAX_TIMESTAMP_FUTURE * 1_000L;
-
-
- /////////////////////////////////fields for solidification thread//////////////////////////////////////
-
- private Thread newSolidThread;
-
- /**
- * If true use {@link #newSolidTransactionsOne} while solidifying. Else use {@link #newSolidTransactionsTwo}.
- */
- private final AtomicBoolean useFirst = new AtomicBoolean(true);
- /**
- * Is {@link #newSolidThread} shutting down
- */
- private final AtomicBoolean shuttingDown = new AtomicBoolean(false);
- /**
- * mutex for solidification
- */
- private final Object cascadeSync = new Object();
- private final Set newSolidTransactionsOne = new LinkedHashSet<>();
- private final Set newSolidTransactionsTwo = new LinkedHashSet<>();
-
- /**
- * Constructor for Tangle Validator
- *
- * @param tangle relays tangle data to and from the persistence layer
- * @param snapshotProvider data provider for the snapshots that are relevant for the node
- * @param tipsViewModel container that gets updated with the latest tips (transactions with no children)
- * @param transactionRequester used to request missing transactions from neighbors
- * @param protocolConfig used for checking if we are in testnet and mwm. testnet true if we are in testnet
- * mode, this caps {@code mwm} to {@value #TESTNET_MWM_CAP} regardless of parameter input.
- * minimum weight magnitude: the minimal number of 9s that ought to appear at the end of the
- * transaction hash
- */
- TransactionValidator(Tangle tangle, SnapshotProvider snapshotProvider, TipsViewModel tipsViewModel, TransactionRequester transactionRequester, ProtocolConfig protocolConfig) {
- this.tangle = tangle;
- this.snapshotProvider = snapshotProvider;
- this.tipsViewModel = tipsViewModel;
- this.transactionRequester = transactionRequester;
- this.newSolidThread = new Thread(spawnSolidTransactionsPropagation(), "Solid TX cascader");
- setMwm(protocolConfig.isTestnet(), protocolConfig.getMwm());
- }
-
- /**
- * Does two things:
- *
- * - Sets the minimum weight magnitude (MWM). POW on a transaction is validated by counting a certain
- * number of consecutive 9s in the end of the transaction hash. The number of 9s is the MWM.
- * - Starts the transaction solidification thread.
- *
- *
- *
- * @see #spawnSolidTransactionsPropagation()
- */
- public void init() {
- newSolidThread.start();
- }
-
- @VisibleForTesting
- void setMwm(boolean testnet, int mwm) {
- minWeightMagnitude = mwm;
-
- //lowest allowed MWM encoded in 46 bytes.
- if (!testnet){
- minWeightMagnitude = Math.max(minWeightMagnitude, TESTNET_MWM_CAP);
- }
- }
-
- /**
- * Shutdown roots to tip solidification thread
- * @throws InterruptedException
- * @see #spawnSolidTransactionsPropagation()
- */
- public void shutdown() throws InterruptedException {
- shuttingDown.set(true);
- newSolidThread.join();
- }
-
- /**
- * @return the minimal number of trailing 9s that have to be present at the end of the transaction hash
- * in order to validate that sufficient proof of work has been done
- */
- public int getMinWeightMagnitude() {
- return minWeightMagnitude;
- }
-
- /**
- * Checks that the timestamp of the transaction is below the last global snapshot time
- * or more than {@value #MAX_TIMESTAMP_FUTURE} seconds in the future, and thus invalid.
- *
- *
- * First the attachment timestamp (set after performing POW) is checked, and if not available
- * the regular timestamp is checked. Genesis transaction will always be valid.
- *
- * @param transactionViewModel transaction under test
- * @return true if timestamp is not in valid bounds and {@code transactionViewModel} is not genesis.
- * Else returns false.
- */
- private boolean hasInvalidTimestamp(TransactionViewModel transactionViewModel) {
- // ignore invalid timestamps for transactions that were requested by our node while solidifying a milestone
- if(transactionRequester.wasTransactionRecentlyRequested(transactionViewModel.getHash())) {
- return false;
- }
-
- if (transactionViewModel.getAttachmentTimestamp() == 0) {
- return transactionViewModel.getTimestamp() < snapshotProvider.getInitialSnapshot().getTimestamp() && !snapshotProvider.getInitialSnapshot().hasSolidEntryPoint(transactionViewModel.getHash())
- || transactionViewModel.getTimestamp() > (System.currentTimeMillis() / 1000) + MAX_TIMESTAMP_FUTURE;
- }
- return transactionViewModel.getAttachmentTimestamp() < (snapshotProvider.getInitialSnapshot().getTimestamp() * 1000L)
- || transactionViewModel.getAttachmentTimestamp() > System.currentTimeMillis() + MAX_TIMESTAMP_FUTURE_MS;
- }
-
- /**
- * Runs the following validation checks on a transaction:
- *
- * - {@link #hasInvalidTimestamp} check.
- * - Check that no value trits are set beyond the usable index, otherwise we will have values larger
- * than max supply.
- * - Check that sufficient POW was performed.
- * - In value transactions, we check that the address has 0 set as the last trit. This must be because of the
- * conversion between bytes to trits.
- *
- *Exception is thrown upon failure.
- *
- * @param transactionViewModel transaction that should be validated
- * @param minWeightMagnitude the minimal number of trailing 9s at the end of the transaction hash
- * @throws StaleTimestampException if timestamp check fails
- * @throws IllegalStateException if any of the other checks fail
- */
- public void runValidation(TransactionViewModel transactionViewModel, final int minWeightMagnitude) {
- transactionViewModel.setMetadata();
- transactionViewModel.setAttachmentData();
- if(hasInvalidTimestamp(transactionViewModel)) {
- throw new StaleTimestampException("Invalid transaction timestamp.");
- }
- for (int i = VALUE_TRINARY_OFFSET + VALUE_USABLE_TRINARY_SIZE; i < VALUE_TRINARY_OFFSET + VALUE_TRINARY_SIZE; i++) {
- if (transactionViewModel.trits()[i] != 0) {
- throw new IllegalStateException("Invalid transaction value");
- }
- }
-
- int weightMagnitude = transactionViewModel.weightMagnitude;
- if(weightMagnitude < minWeightMagnitude) {
- throw new IllegalStateException("Invalid weight magnitude");
- }
-
- if (transactionViewModel.value() != 0 && transactionViewModel.getAddressHash().trits()[Curl.HASH_LENGTH - 1] != 0) {
- throw new IllegalStateException("Invalid transaction address");
- }
- }
-
- /**
- * Creates a new transaction from {@code trits} and validates it with {@link #runValidation}.
- *
- * @param trits raw transaction trits
- * @param minWeightMagnitude minimal number of trailing 9s in transaction for POW validation
- * @return the transaction resulting from the raw trits if valid.
- * @throws RuntimeException if validation fails
- */
- public TransactionViewModel validateTrits(final byte[] trits, int minWeightMagnitude) {
- TransactionViewModel transactionViewModel = new TransactionViewModel(trits, TransactionHash.calculate(trits, 0, trits.length, SpongeFactory.create(SpongeFactory.Mode.CURLP81)));
- runValidation(transactionViewModel, minWeightMagnitude);
- return transactionViewModel;
- }
-
- /**
- * Creates a new transaction from {@code bytes} and validates it with {@link #runValidation}.
- *
- * @param bytes raw transaction bytes
- * @param minWeightMagnitude minimal number of trailing 9s in transaction for POW validation
- * @return the transaction resulting from the raw bytes if valid
- * @throws RuntimeException if validation fails
- */
- public TransactionViewModel validateBytes(final byte[] bytes, int minWeightMagnitude, Sponge curl) {
- TransactionViewModel transactionViewModel = new TransactionViewModel(bytes, TransactionHash.calculate(bytes, TRINARY_SIZE, curl));
- runValidation(transactionViewModel, minWeightMagnitude);
- return transactionViewModel;
- }
-
- /**
- * This method does the same as {@link #checkSolidity(Hash, int)} but defaults to an unlimited amount
- * of transactions that are allowed to be traversed.
- *
- * @param hash hash of the transactions that shall get checked
- * @return true if the transaction is solid and false otherwise
- * @throws Exception if anything goes wrong while trying to solidify the transaction
- */
- public boolean checkSolidity(Hash hash) throws Exception {
- return checkSolidity(hash, Integer.MAX_VALUE);
- }
-
- /**
- * This method checks transactions for solidity and marks them accordingly if they are found to be solid.
- *
- * It iterates through all approved transactions until it finds one that is missing in the database or until it
- * reached solid transactions on all traversed subtangles. In case of a missing transactions it issues a transaction
- * request and returns false. If no missing transaction is found, it marks the processed transactions as solid in
- * the database and returns true.
- *
- * Since this operation can potentially take a long time to terminate if it would have to traverse big parts of the
- * tangle, it is possible to limit the amount of transactions that are allowed to be processed, while looking for
- * unsolid / missing approvees. This can be useful when trying to "interrupt" the solidification of one transaction
- * (if it takes too many steps) to give another one the chance to be solidified instead (i.e. prevent blocks in the
- * solidification threads).
- *
- * @param hash hash of the transactions that shall get checked
- * @param maxProcessedTransactions the maximum amount of transactions that are allowed to be traversed
- * @return true if the transaction is solid and false otherwise
- * @throws Exception if anything goes wrong while trying to solidify the transaction
- */
- public boolean checkSolidity(Hash hash, int maxProcessedTransactions) throws Exception {
- if(fromHash(tangle, hash).isSolid()) {
- return true;
- }
- LinkedHashSet analyzedHashes = new LinkedHashSet<>(snapshotProvider.getInitialSnapshot().getSolidEntryPoints().keySet());
- if(maxProcessedTransactions != Integer.MAX_VALUE) {
- maxProcessedTransactions += analyzedHashes.size();
- }
- boolean solid = true;
- final Queue nonAnalyzedTransactions = new LinkedList<>(Collections.singleton(hash));
- Hash hashPointer;
- while ((hashPointer = nonAnalyzedTransactions.poll()) != null) {
- if (!analyzedHashes.add(hashPointer)) {
- continue;
- }
-
- if (analyzedHashes.size() >= maxProcessedTransactions) {
- return false;
- }
-
- TransactionViewModel transaction = fromHash(tangle, hashPointer);
- if (!transaction.isSolid() && !snapshotProvider.getInitialSnapshot().hasSolidEntryPoint(hashPointer)) {
- if (transaction.getType() == PREFILLED_SLOT) {
- solid = false;
-
- if (!transactionRequester.isTransactionRequested(hashPointer)) {
- transactionRequester.requestTransaction(hashPointer);
- continue;
- }
- } else {
- nonAnalyzedTransactions.offer(transaction.getTrunkTransactionHash());
- nonAnalyzedTransactions.offer(transaction.getBranchTransactionHash());
- }
- }
- }
- if (solid) {
- updateSolidTransactions(tangle, snapshotProvider.getInitialSnapshot(), analyzedHashes);
- }
- analyzedHashes.clear();
- return solid;
- }
-
- public void addSolidTransaction(Hash hash) {
- synchronized (cascadeSync) {
- if (useFirst.get()) {
- newSolidTransactionsOne.add(hash);
- } else {
- newSolidTransactionsTwo.add(hash);
- }
- }
- }
-
- /**
- * Creates a runnable that runs {@link #propagateSolidTransactions()} in a loop every {@value #SOLID_SLEEP_TIME} ms
- * @return runnable that is not started
- */
- private Runnable spawnSolidTransactionsPropagation() {
- return () -> {
- while(!shuttingDown.get()) {
- propagateSolidTransactions();
- try {
- Thread.sleep(SOLID_SLEEP_TIME);
- } catch (InterruptedException e) {
- // Ignoring InterruptedException. Do not use Thread.currentThread().interrupt() here.
- log.error("Thread was interrupted: ", e);
- }
- }
- };
- }
-
- /**
- * Iterates over all currently known solid transactions. For each solid transaction, we find
- * its children (approvers) and try to quickly solidify them with {@link #quietQuickSetSolid}.
- * If we manage to solidify the transactions, we add them to the solidification queue for a traversal by a later run.
- */
- @VisibleForTesting
- void propagateSolidTransactions() {
- Set newSolidHashes = new HashSet<>();
- useFirst.set(!useFirst.get());
- //synchronized to make sure no one is changing the newSolidTransactions collections during addAll
- synchronized (cascadeSync) {
- //We are using a collection that doesn't get updated by other threads
- if (useFirst.get()) {
- newSolidHashes.addAll(newSolidTransactionsTwo);
- newSolidTransactionsTwo.clear();
- } else {
- newSolidHashes.addAll(newSolidTransactionsOne);
- newSolidTransactionsOne.clear();
- }
- }
- Iterator cascadeIterator = newSolidHashes.iterator();
- while(cascadeIterator.hasNext() && !shuttingDown.get()) {
- try {
- Hash hash = cascadeIterator.next();
- TransactionViewModel transaction = fromHash(tangle, hash);
- Set approvers = transaction.getApprovers(tangle).getHashes();
- for(Hash h: approvers) {
- TransactionViewModel tx = fromHash(tangle, h);
- if(quietQuickSetSolid(tx)) {
- tx.update(tangle, snapshotProvider.getInitialSnapshot(), "solid|height");
- tipsViewModel.setSolid(h);
- addSolidTransaction(h);
- }
- }
- } catch (Exception e) {
- log.error("Error while propagating solidity upwards", e);
- }
- }
- }
-
-
- /**
- * Updates a transaction after it was stored in the tangle. Tells the node to not request the transaction anymore,
- * to update the live tips accordingly, and attempts to quickly solidify the transaction.
- *
- *
- * Performs the following operations:
- *
- *
- * - Removes {@code transactionViewModel}'s hash from the the request queue since we already found it.
- * - If {@code transactionViewModel} has no children (approvers), we add it to the node's active tip list.
- * - Removes {@code transactionViewModel}'s parents (branch & trunk) from the node's tip list
- * (if they're present there).
- * - Attempts to quickly solidify {@code transactionViewModel} by checking whether its direct parents
- * are solid. If solid we add it to the queue transaction solidification thread to help it propagate the
- * solidification to the approving child transactions.
- * - Requests missing direct parent (trunk & branch) transactions that are needed to solidify
- * {@code transactionViewModel}.
- *
- * @param transactionViewModel received transaction that is being updated
- * @throws Exception if an error occurred while trying to solidify
- * @see TipsViewModel
- */
- //Not part of the validation process. This should be moved to a component in charge of
- //what transaction we gossip.
- public void updateStatus(TransactionViewModel transactionViewModel) throws Exception {
- transactionRequester.clearTransactionRequest(transactionViewModel.getHash());
- if(transactionViewModel.getApprovers(tangle).size() == 0) {
- tipsViewModel.addTipHash(transactionViewModel.getHash());
- }
- tipsViewModel.removeTipHash(transactionViewModel.getTrunkTransactionHash());
- tipsViewModel.removeTipHash(transactionViewModel.getBranchTransactionHash());
-
- if(quickSetSolid(transactionViewModel)) {
- transactionViewModel.update(tangle, snapshotProvider.getInitialSnapshot(), "solid|height");
- tipsViewModel.setSolid(transactionViewModel.getHash());
- addSolidTransaction(transactionViewModel.getHash());
- }
- }
-
- /**
- * Perform a {@link #quickSetSolid} while capturing and logging errors
- * @param transactionViewModel transaction we try to solidify.
- * @return true if we managed to solidify, else false.
- */
- private boolean quietQuickSetSolid(TransactionViewModel transactionViewModel) {
- try {
- return quickSetSolid(transactionViewModel);
- } catch (Exception e) {
- log.error(e.getMessage(), e);
- return false;
- }
- }
-
- /**
- * Tries to solidify the transactions quickly by performing {@link #checkApproovee} on both parents (trunk and
- * branch). If the parents are solid, mark the transactions as solid.
- * @param transactionViewModel transaction to solidify
- * @return true if we made the transaction solid, else false.
- * @throws Exception
- */
- private boolean quickSetSolid(final TransactionViewModel transactionViewModel) throws Exception {
- if(!transactionViewModel.isSolid()) {
- boolean solid = true;
- if (!checkApproovee(transactionViewModel.getTrunkTransaction(tangle))) {
- solid = false;
- }
- if (!checkApproovee(transactionViewModel.getBranchTransaction(tangle))) {
- solid = false;
- }
- if(solid) {
- transactionViewModel.updateSolid(true);
- transactionViewModel.updateHeights(tangle, snapshotProvider.getInitialSnapshot());
- return true;
- }
- }
- return false;
- }
-
- /**
- * If the the {@code approvee} is missing, request it from a neighbor.
- * @param approovee transaction we check.
- * @return true if {@code approvee} is solid.
- * @throws Exception if we encounter an error while requesting a transaction
- */
- private boolean checkApproovee(TransactionViewModel approovee) throws Exception {
- if(snapshotProvider.getInitialSnapshot().hasSolidEntryPoint(approovee.getHash())) {
- return true;
- }
- if(approovee.getType() == PREFILLED_SLOT) {
- // don't solidify from the bottom until cuckoo filters can identify where we deleted -> otherwise we will
- // continue requesting old transactions forever
- //transactionRequester.requestTransaction(approovee.getHash(), false);
- return false;
- }
- return approovee.isSolid();
- }
-
- @VisibleForTesting
- boolean isNewSolidTxSetsEmpty () {
- return newSolidTransactionsOne.isEmpty() && newSolidTransactionsTwo.isEmpty();
- }
-
- /**
- * Thrown if transaction fails {@link #hasInvalidTimestamp} check.
- */
- public static class StaleTimestampException extends RuntimeException {
- StaleTimestampException (String message) {
- super(message);
- }
- }
-}
diff --git a/src/main/java/com/iota/iri/controllers/AddressViewModel.java b/src/main/java/com/iota/iri/controllers/AddressViewModel.java
index ba2a9527ec..77036578c4 100644
--- a/src/main/java/com/iota/iri/controllers/AddressViewModel.java
+++ b/src/main/java/com/iota/iri/controllers/AddressViewModel.java
@@ -7,13 +7,8 @@
import com.iota.iri.storage.Persistable;
import com.iota.iri.storage.Tangle;
import com.iota.iri.utils.Pair;
-import pl.touk.throwing.ThrowingFunction;
-import pl.touk.throwing.ThrowingPredicate;
-import java.util.Comparator;
-import java.util.List;
import java.util.Set;
-import java.util.stream.Collectors;
/**
* Acts as a controller interface for an {@link Address} set. This controller is used within a
@@ -43,25 +38,6 @@ private AddressViewModel(Address hashes, Indexable hash) {
this.hash = hash;
}
- /**
- * Loads all transaction that mutate a certain address. It sorts them by the attachment timestamp. Sorting by
- * attachmentTimeStamp is an arbitrary choice.
- *
- *
- * @param tangle The tangle reference for the database to find the {@link Address} set in
- * @param hash hash The address we are loading the transactions for
- * @return The list of {@link AddressViewModel} controllers generated
- * @throws Exception Thrown if the database cannot load an {@link Address} set from the reference {@link Hash}
- */
- public static List loadAsSortedList(Tangle tangle, Indexable hash) throws Exception {
- Address hashes = (Address) tangle.load(Address.class, hash);
- return hashes.set.stream()
- .filter(ThrowingPredicate.unchecked(hash1 -> TransactionViewModel.exists(tangle, hash1)))
- .map(ThrowingFunction.unchecked(item -> TransactionViewModel.fromHash(tangle, item)))
- .sorted(Comparator.comparing(TransactionViewModel::getAttachmentTimestamp))
- .collect(Collectors.toList());
- }
-
/**
* Creates a new {@link Address} set controller. This controller is created by extracting the {@link Address} set
* from the database using the provided {@link Hash} identifier.
diff --git a/src/main/java/com/iota/iri/controllers/TransactionViewModel.java b/src/main/java/com/iota/iri/controllers/TransactionViewModel.java
index 89e78407ed..0beb399048 100644
--- a/src/main/java/com/iota/iri/controllers/TransactionViewModel.java
+++ b/src/main/java/com/iota/iri/controllers/TransactionViewModel.java
@@ -5,6 +5,7 @@
import com.iota.iri.model.*;
import com.iota.iri.model.persistables.*;
import com.iota.iri.service.snapshot.Snapshot;
+import com.iota.iri.service.validation.TransactionValidator;
import com.iota.iri.storage.Indexable;
import com.iota.iri.storage.Persistable;
import com.iota.iri.storage.Tangle;
@@ -778,33 +779,11 @@ public void setMetadata() {
: TransactionViewModel.FILLED_SLOT;
}
- /**
- * Update solid transactions
- * @param tangle Tangle
- * @param initialSnapshot Initial snapshot
- * @param analyzedHashes analyzed hashes
- * @throws Exception Exception
- */
- public static void updateSolidTransactions(Tangle tangle, Snapshot initialSnapshot,
- final Set analyzedHashes) throws Exception {
- Object[] hashes = analyzedHashes.toArray();
- TransactionViewModel transactionViewModel;
- for (int i = hashes.length - 1; i >= 0; i--) {
- transactionViewModel = TransactionViewModel.fromHash(tangle, (Hash) hashes[i]);
-
- transactionViewModel.updateHeights(tangle, initialSnapshot);
-
- if (!transactionViewModel.isSolid()) {
- transactionViewModel.updateSolid(true);
- transactionViewModel.update(tangle, initialSnapshot, "solid|height");
- }
- }
- }
/**
* Updates the {@link Transaction#solid} value of the referenced {@link Transaction} object.
*
- * Used by the {@link com.iota.iri.TransactionValidator} to quickly set the solidity of a {@link Transaction} set.
+ * Used by the {@link TransactionValidator} to quickly set the solidity of a {@link Transaction} set.
*
* @param solid The solidity of the transaction in the database
* @return True if the {@link Transaction#solid} has been updated, False if not.
diff --git a/src/main/java/com/iota/iri/network/NetworkInjectionConfiguration.java b/src/main/java/com/iota/iri/network/NetworkInjectionConfiguration.java
index 032715736d..d4caa618e5 100644
--- a/src/main/java/com/iota/iri/network/NetworkInjectionConfiguration.java
+++ b/src/main/java/com/iota/iri/network/NetworkInjectionConfiguration.java
@@ -3,7 +3,8 @@
import com.google.inject.AbstractModule;
import com.google.inject.Provides;
import com.google.inject.Singleton;
-import com.iota.iri.TransactionValidator;
+import com.iota.iri.service.validation.TransactionSolidifier;
+import com.iota.iri.service.validation.TransactionValidator;
import com.iota.iri.conf.IotaConfig;
import com.iota.iri.controllers.TipsViewModel;
import com.iota.iri.network.impl.TipsRequesterImpl;
@@ -46,9 +47,9 @@ TipsRequester provideTipsRequester(NeighborRouter neighborRouter, Tangle tangle,
TransactionProcessingPipeline provideTransactionProcessingPipeline(NeighborRouter neighborRouter,
TransactionValidator txValidator, Tangle tangle, SnapshotProvider snapshotProvider,
TipsViewModel tipsViewModel, LatestMilestoneTracker latestMilestoneTracker,
- TransactionRequester transactionRequester) {
+ TransactionRequester transactionRequester, TransactionSolidifier transactionSolidifier) {
return new TransactionProcessingPipelineImpl(neighborRouter, configuration, txValidator, tangle,
- snapshotProvider, tipsViewModel, latestMilestoneTracker, transactionRequester);
+ snapshotProvider, tipsViewModel, latestMilestoneTracker, transactionRequester, transactionSolidifier);
}
@Singleton
diff --git a/src/main/java/com/iota/iri/network/pipeline/ReceivedStage.java b/src/main/java/com/iota/iri/network/pipeline/ReceivedStage.java
index e8c5e99711..e95d051bca 100644
--- a/src/main/java/com/iota/iri/network/pipeline/ReceivedStage.java
+++ b/src/main/java/com/iota/iri/network/pipeline/ReceivedStage.java
@@ -1,6 +1,6 @@
package com.iota.iri.network.pipeline;
-import com.iota.iri.TransactionValidator;
+import com.iota.iri.service.validation.TransactionSolidifier;
import com.iota.iri.controllers.TransactionViewModel;
import com.iota.iri.network.TransactionRequester;
import com.iota.iri.network.neighbor.Neighbor;
@@ -19,19 +19,19 @@ public class ReceivedStage implements Stage {
private Tangle tangle;
private TransactionRequester transactionRequester;
- private TransactionValidator txValidator;
+ private TransactionSolidifier txSolidifier;
private SnapshotProvider snapshotProvider;
/**
* Creates a new {@link ReceivedStage}.
*
* @param tangle The {@link Tangle} database used to store/update the transaction
- * @param txValidator The {@link TransactionValidator} used to store/update the transaction
+ * @param txSolidifier The {@link TransactionSolidifier} used to store/update the transaction
* @param snapshotProvider The {@link SnapshotProvider} used to store/update the transaction
*/
- public ReceivedStage(Tangle tangle, TransactionValidator txValidator, SnapshotProvider snapshotProvider,
+ public ReceivedStage(Tangle tangle, TransactionSolidifier txSolidifier, SnapshotProvider snapshotProvider,
TransactionRequester transactionRequester) {
- this.txValidator = txValidator;
+ this.txSolidifier = txSolidifier;
this.tangle = tangle;
this.snapshotProvider = snapshotProvider;
this.transactionRequester = transactionRequester;
@@ -39,7 +39,7 @@ public ReceivedStage(Tangle tangle, TransactionValidator txValidator, SnapshotPr
/**
* Stores the given transaction in the database, updates it status
- * ({@link TransactionValidator#updateStatus(TransactionViewModel)}) and updates the sender.
+ * ({@link TransactionSolidifier#updateStatus(TransactionViewModel)}) and updates the sender.
*
* @param ctx the received stage {@link ProcessingContext}
* @return a {@link ProcessingContext} which redirects to the {@link BroadcastStage}
@@ -65,7 +65,7 @@ public ProcessingContext process(ProcessingContext ctx) {
if (stored) {
tvm.setArrivalTime(System.currentTimeMillis());
try {
- txValidator.updateStatus(tvm);
+ txSolidifier.updateStatus(tvm);
// free up the recently requested transaction set
if(transactionRequester.removeRecentlyRequestedTransaction(tvm.getHash())){
@@ -91,8 +91,8 @@ public ProcessingContext process(ProcessingContext ctx) {
}
// broadcast the newly saved tx to the other neighbors
- ctx.setNextStage(TransactionProcessingPipeline.Stage.BROADCAST);
- ctx.setPayload(new BroadcastPayload(originNeighbor, tvm));
+ ctx.setNextStage(TransactionProcessingPipeline.Stage.SOLIDIFY);
+ ctx.setPayload(new SolidifyPayload(originNeighbor, tvm));
return ctx;
}
}
diff --git a/src/main/java/com/iota/iri/network/pipeline/SolidifyPayload.java b/src/main/java/com/iota/iri/network/pipeline/SolidifyPayload.java
new file mode 100644
index 0000000000..718a2c88bb
--- /dev/null
+++ b/src/main/java/com/iota/iri/network/pipeline/SolidifyPayload.java
@@ -0,0 +1,39 @@
+package com.iota.iri.network.pipeline;
+
+import com.iota.iri.controllers.TransactionViewModel;
+import com.iota.iri.network.neighbor.Neighbor;
+
+/**
+ * Defines a payload which gets submitted to the {@link SolidifyStage}.
+ */
+public class SolidifyPayload extends Payload {
+ private Neighbor originNeighbor;
+ private TransactionViewModel tvm;
+
+ /**
+ * Constructor for solidification payload.
+ *
+ * @param originNeighbor The originating point of a received transaction
+ * @param tvm The transaction that needs to be solidified
+ */
+ public SolidifyPayload(Neighbor originNeighbor, TransactionViewModel tvm){
+ this.originNeighbor = originNeighbor;
+ this.tvm = tvm;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Neighbor getOriginNeighbor(){
+ return originNeighbor;
+ }
+
+ /**
+ * Fetches the transaction from the payload.
+ * @return The transaction stored in the payload.
+ */
+ public TransactionViewModel getTransaction(){
+ return tvm;
+ }
+}
diff --git a/src/main/java/com/iota/iri/network/pipeline/SolidifyStage.java b/src/main/java/com/iota/iri/network/pipeline/SolidifyStage.java
new file mode 100644
index 0000000000..85d4ee85ac
--- /dev/null
+++ b/src/main/java/com/iota/iri/network/pipeline/SolidifyStage.java
@@ -0,0 +1,97 @@
+package com.iota.iri.network.pipeline;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.iota.iri.controllers.TipsViewModel;
+import com.iota.iri.controllers.TransactionViewModel;
+import com.iota.iri.model.Hash;
+import com.iota.iri.service.validation.TransactionSolidifier;
+import com.iota.iri.storage.Tangle;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static com.iota.iri.controllers.TransactionViewModel.fromHash;
+
+/**
+ * The {@link SolidifyStage} is used to process newly received transaction for solidity. Once a transaction has been
+ * passed from the {@link ReceivedStage} it will be placed into this stage to have the {@link TransactionSolidifier}
+ * check the solidity of the transaction. If the transaction is found to be solid, it will be passed forward to the
+ * {@link BroadcastStage}. If it is found to be unsolid, it is put through the solidity check so that missing reference
+ * transactions get requested. If the transaction is unsolid, a random solid tip is broadcast instead to keep the
+ * requests transmitting to neighbors.
+ */
+public class SolidifyStage implements Stage {
+ private static final Logger log = LoggerFactory.getLogger(SolidifyStage.class);
+
+ private TransactionSolidifier txSolidifier;
+ private TipsViewModel tipsViewModel;
+ private Tangle tangle;
+ private TransactionViewModel tip;
+
+ /**
+ * Constructor for the {@link SolidifyStage}.
+ *
+ * @param txSolidifier Transaction validator implementation for determining the validity of a transaction
+ * @param tipsViewModel Used for broadcasting random solid tips if the subject transaction is unsolid
+ * @param tangle A reference to the nodes DB
+ */
+ public SolidifyStage(TransactionSolidifier txSolidifier, TipsViewModel tipsViewModel, Tangle tangle){
+ this.txSolidifier = txSolidifier;
+ this.tipsViewModel = tipsViewModel;
+ this.tangle = tangle;
+ }
+
+ /**
+ * Processes the payload of the {@link ProcessingContext} as a {@link SolidifyPayload}. First the transaction will
+ * be checked for solidity and validity. If the transaction is already solid or can be set solid quickly by the
+ * transaction validator, the transaction is passed to the {@link BroadcastStage}. If not, a random solid tip is
+ * pulled form the {@link TipsViewModel} to be broadcast instead.
+ *
+ * @param ctx The context to process
+ * @return The output context, in most cases a {@link BroadcastPayload}.
+ */
+ @Override
+ public ProcessingContext process(ProcessingContext ctx){
+ try {
+ SolidifyPayload payload = (SolidifyPayload) ctx.getPayload();
+ TransactionViewModel tvm = payload.getTransaction();
+
+ if (tvm.isSolid() || txSolidifier.quickSetSolid(tvm)) {
+ ctx.setNextStage(TransactionProcessingPipeline.Stage.BROADCAST);
+ ctx.setPayload(new BroadcastPayload(payload.getOriginNeighbor(), payload.getTransaction()));
+ return ctx;
+ }
+
+ return broadcastTip(ctx, payload);
+ }catch (Exception e){
+ log.error("Failed to process transaction for solidification", e);
+ ctx.setNextStage(TransactionProcessingPipeline.Stage.ABORT);
+ return ctx;
+ }
+
+ }
+
+ private ProcessingContext broadcastTip(ProcessingContext ctx, SolidifyPayload payload) throws Exception{
+ if(tip == null) {
+ Hash tipHash = tipsViewModel.getRandomSolidTipHash();
+
+ if (tipHash == null) {
+ ctx.setNextStage(TransactionProcessingPipeline.Stage.FINISH);
+ return ctx;
+ }
+
+ tip = fromHash(tangle, tipHash);
+ }
+
+ ctx.setNextStage(TransactionProcessingPipeline.Stage.BROADCAST);
+ ctx.setPayload(new BroadcastPayload(payload.getOriginNeighbor(), tip));
+
+ tip = null;
+ return ctx;
+ }
+
+ @VisibleForTesting
+ void injectTip(TransactionViewModel tvm) throws Exception {
+ tip = tvm;
+ tip.updateSolid(true);
+ }
+}
diff --git a/src/main/java/com/iota/iri/network/pipeline/TransactionProcessingPipeline.java b/src/main/java/com/iota/iri/network/pipeline/TransactionProcessingPipeline.java
index 1f19e4248c..6a659ef7a9 100644
--- a/src/main/java/com/iota/iri/network/pipeline/TransactionProcessingPipeline.java
+++ b/src/main/java/com/iota/iri/network/pipeline/TransactionProcessingPipeline.java
@@ -1,6 +1,7 @@
package com.iota.iri.network.pipeline;
import com.iota.iri.network.neighbor.Neighbor;
+import com.iota.iri.service.validation.TransactionSolidifier;
import java.nio.ByteBuffer;
import java.util.concurrent.BlockingQueue;
@@ -14,7 +15,7 @@ public interface TransactionProcessingPipeline {
* Defines the different stages of the {@link TransactionProcessingPipelineImpl}.
*/
enum Stage {
- PRE_PROCESS, HASHING, VALIDATION, REPLY, RECEIVED, BROADCAST, MULTIPLE, ABORT, FINISH,
+ PRE_PROCESS, HASHING, VALIDATION, REPLY, RECEIVED, BROADCAST, MULTIPLE, ABORT, FINISH, SOLIDIFY,
}
/**
@@ -65,6 +66,12 @@ enum Stage {
*/
void process(byte[] txTrits);
+ /**
+ * Fetches a set of transactions from the {@link TransactionSolidifier} and submits
+ * the object into the {@link BroadcastStage} queue.
+ */
+ void refillBroadcastQueue();
+
/**
* Shut downs the pipeline by shutting down all stages.
*/
@@ -111,4 +118,11 @@ enum Stage {
* @param hashingStage the {@link HashingStage} to use
*/
void setHashingStage(HashingStage hashingStage);
+
+ /**
+ * Sets the solidify stage. This method should only be used for injecting mocked objects.
+ *
+ * @param solidifyStage the {@link SolidifyStage} to use
+ */
+ void setSolidifyStage(SolidifyStage solidifyStage);
}
diff --git a/src/main/java/com/iota/iri/network/pipeline/TransactionProcessingPipelineImpl.java b/src/main/java/com/iota/iri/network/pipeline/TransactionProcessingPipelineImpl.java
index 0af71b8420..d736b4c49d 100644
--- a/src/main/java/com/iota/iri/network/pipeline/TransactionProcessingPipelineImpl.java
+++ b/src/main/java/com/iota/iri/network/pipeline/TransactionProcessingPipelineImpl.java
@@ -1,8 +1,10 @@
package com.iota.iri.network.pipeline;
-import com.iota.iri.TransactionValidator;
+import com.iota.iri.service.validation.TransactionSolidifier;
+import com.iota.iri.service.validation.TransactionValidator;
import com.iota.iri.conf.NodeConfig;
import com.iota.iri.controllers.TipsViewModel;
+import com.iota.iri.controllers.TransactionViewModel;
import com.iota.iri.crypto.batched.BatchedHasher;
import com.iota.iri.crypto.batched.BatchedHasherFactory;
import com.iota.iri.crypto.batched.HashRequest;
@@ -19,7 +21,10 @@
import com.iota.iri.utils.Converter;
import java.nio.ByteBuffer;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
@@ -69,12 +74,15 @@ public class TransactionProcessingPipelineImpl implements TransactionProcessingP
private BroadcastStage broadcastStage;
private BatchedHasher batchedHasher;
private HashingStage hashingStage;
+ private SolidifyStage solidifyStage;
+ private TransactionSolidifier txSolidifier;
private BlockingQueue preProcessStageQueue = new ArrayBlockingQueue<>(100);
private BlockingQueue validationStageQueue = new ArrayBlockingQueue<>(100);
private BlockingQueue receivedStageQueue = new ArrayBlockingQueue<>(100);
- private BlockingQueue broadcastStageQueue = new ArrayBlockingQueue<>(100);
private BlockingQueue replyStageQueue = new ArrayBlockingQueue<>(100);
+ private BlockingQueue broadcastStageQueue = new ArrayBlockingQueue<>(100);
+ private BlockingQueue solidifyStageQueue = new ArrayBlockingQueue<>(100);
/**
* Creates a {@link TransactionProcessingPipeline}.
@@ -91,16 +99,18 @@ public class TransactionProcessingPipelineImpl implements TransactionProcessingP
public TransactionProcessingPipelineImpl(NeighborRouter neighborRouter, NodeConfig config,
TransactionValidator txValidator, Tangle tangle, SnapshotProvider snapshotProvider,
TipsViewModel tipsViewModel, LatestMilestoneTracker latestMilestoneTracker,
- TransactionRequester transactionRequester) {
+ TransactionRequester transactionRequester, TransactionSolidifier txSolidifier) {
FIFOCache recentlySeenBytesCache = new FIFOCache<>(config.getCacheSizeBytes());
this.preProcessStage = new PreProcessStage(recentlySeenBytesCache);
this.replyStage = new ReplyStage(neighborRouter, config, tangle, tipsViewModel, latestMilestoneTracker,
snapshotProvider, recentlySeenBytesCache);
this.broadcastStage = new BroadcastStage(neighborRouter);
this.validationStage = new ValidationStage(txValidator, recentlySeenBytesCache);
- this.receivedStage = new ReceivedStage(tangle, txValidator, snapshotProvider, transactionRequester);
+ this.receivedStage = new ReceivedStage(tangle, txSolidifier, snapshotProvider, transactionRequester);
this.batchedHasher = BatchedHasherFactory.create(BatchedHasherFactory.Type.BCTCURL81, 20);
this.hashingStage = new HashingStage(batchedHasher);
+ this.solidifyStage = new SolidifyStage(txSolidifier, tipsViewModel, tangle);
+ this.txSolidifier = txSolidifier;
}
@Override
@@ -111,6 +121,7 @@ public void start() {
addStage("reply", replyStageQueue, replyStage);
addStage("received", receivedStageQueue, receivedStage);
addStage("broadcast", broadcastStageQueue, broadcastStage);
+ addStage("solidify", solidifyStageQueue, solidifyStage);
}
/**
@@ -126,6 +137,7 @@ private void addStage(String name, BlockingQueue queue,
try {
while (!Thread.currentThread().isInterrupted()) {
ProcessingContext ctx = stage.process(queue.take());
+
switch (ctx.getNextStage()) {
case REPLY:
replyStageQueue.put(ctx);
@@ -144,6 +156,9 @@ private void addStage(String name, BlockingQueue queue,
case BROADCAST:
broadcastStageQueue.put(ctx);
break;
+ case SOLIDIFY:
+ solidifyStageQueue.put(ctx);
+ break;
case ABORT:
break;
case FINISH:
@@ -184,6 +199,7 @@ public BlockingQueue getValidationStageQueue() {
public void process(Neighbor neighbor, ByteBuffer data) {
try {
preProcessStageQueue.put(new ProcessingContext(new PreProcessPayload(neighbor, data)));
+ refillBroadcastQueue();
} catch (InterruptedException e) {
e.printStackTrace();
}
@@ -198,6 +214,23 @@ public void process(byte[] txTrits) {
hashAndValidate(new ProcessingContext(payload));
}
+ @Override
+ public void refillBroadcastQueue(){
+ try{
+ Iterator hashIterator = txSolidifier.getBroadcastQueue().iterator();
+ Set toRemove = new LinkedHashSet<>();
+ while(!Thread.currentThread().isInterrupted() && hashIterator.hasNext()){
+ TransactionViewModel tx = hashIterator.next();
+ broadcastStageQueue.put(new ProcessingContext(new BroadcastPayload(null, tx)));
+ toRemove.add(tx);
+ hashIterator.remove();
+ }
+ txSolidifier.clearFromBroadcastQueue(toRemove);
+ } catch(InterruptedException e){
+ log.info(e.getMessage());
+ }
+ }
+
/**
* Sets up the given hashing stage {@link ProcessingContext} so that up on success, it will submit further to the
* validation stage.
@@ -255,4 +288,9 @@ public void setBroadcastStage(BroadcastStage broadcastStage) {
public void setHashingStage(HashingStage hashingStage) {
this.hashingStage = hashingStage;
}
+
+ @Override
+ public void setSolidifyStage(SolidifyStage solidifyStage){
+ this.solidifyStage = solidifyStage;
+ }
}
diff --git a/src/main/java/com/iota/iri/network/pipeline/ValidationStage.java b/src/main/java/com/iota/iri/network/pipeline/ValidationStage.java
index a139210eb4..47c41fa3a9 100644
--- a/src/main/java/com/iota/iri/network/pipeline/ValidationStage.java
+++ b/src/main/java/com/iota/iri/network/pipeline/ValidationStage.java
@@ -1,6 +1,6 @@
package com.iota.iri.network.pipeline;
-import com.iota.iri.TransactionValidator;
+import com.iota.iri.service.validation.TransactionValidator;
import com.iota.iri.controllers.TransactionViewModel;
import com.iota.iri.model.Hash;
import com.iota.iri.model.HashFactory;
diff --git a/src/main/java/com/iota/iri/service/API.java b/src/main/java/com/iota/iri/service/API.java
index e3828e9d41..78a01d960e 100644
--- a/src/main/java/com/iota/iri/service/API.java
+++ b/src/main/java/com/iota/iri/service/API.java
@@ -6,7 +6,8 @@
import com.iota.iri.BundleValidator;
import com.iota.iri.IRI;
import com.iota.iri.IXI;
-import com.iota.iri.TransactionValidator;
+import com.iota.iri.service.validation.TransactionValidator;
+import com.iota.iri.service.validation.TransactionSolidifier;
import com.iota.iri.conf.APIConfig;
import com.iota.iri.conf.IotaConfig;
import com.iota.iri.controllers.*;
@@ -108,6 +109,7 @@ public class API {
private final TipSelector tipsSelector;
private final TipsViewModel tipsViewModel;
private final TransactionValidator transactionValidator;
+ private final TransactionSolidifier transactionSolidifier;
private final LatestMilestoneTracker latestMilestoneTracker;
private final int maxFindTxs;
@@ -149,12 +151,16 @@ public class API {
* @param tipsViewModel Contains the current tips of this node
* @param transactionValidator Validates transactions
* @param latestMilestoneTracker Service that tracks the latest milestone
+ * @param transactionSolidifier Holds transaction pipeline, including broadcast transactions
+ * @param t Service that tracks the latest milestone
+ *
*/
public API(IotaConfig configuration, IXI ixi, TransactionRequester transactionRequester,
SpentAddressesService spentAddressesService, Tangle tangle, BundleValidator bundleValidator,
- SnapshotProvider snapshotProvider, LedgerService ledgerService, NeighborRouter neighborRouter, TipSelector tipsSelector,
- TipsViewModel tipsViewModel, TransactionValidator transactionValidator,
- LatestMilestoneTracker latestMilestoneTracker, TransactionProcessingPipeline txPipeline) {
+ SnapshotProvider snapshotProvider, LedgerService ledgerService, NeighborRouter neighborRouter,
+ TipSelector tipsSelector, TipsViewModel tipsViewModel, TransactionValidator transactionValidator,
+ LatestMilestoneTracker latestMilestoneTracker, TransactionProcessingPipeline txPipeline,
+ TransactionSolidifier transactionSolidifier) {
this.configuration = configuration;
this.ixi = ixi;
@@ -169,6 +175,7 @@ public API(IotaConfig configuration, IXI ixi, TransactionRequester transactionRe
this.tipsSelector = tipsSelector;
this.tipsViewModel = tipsViewModel;
this.transactionValidator = transactionValidator;
+ this.transactionSolidifier = transactionSolidifier;
this.latestMilestoneTracker = latestMilestoneTracker;
maxFindTxs = configuration.getMaxFindTransactions();
@@ -683,7 +690,7 @@ public AbstractResponse storeTransactionsStatement(List trytes) throws E
//store transactions
if(transactionViewModel.store(tangle, snapshotProvider.getInitialSnapshot())) {
transactionViewModel.setArrivalTime(System.currentTimeMillis());
- transactionValidator.updateStatus(transactionViewModel);
+ transactionSolidifier.updateStatus(transactionViewModel);
transactionViewModel.updateSender("local");
transactionViewModel.update(tangle, snapshotProvider.getInitialSnapshot(), "sender");
}
diff --git a/src/main/java/com/iota/iri/service/milestone/impl/LatestMilestoneTrackerImpl.java b/src/main/java/com/iota/iri/service/milestone/impl/LatestMilestoneTrackerImpl.java
index f25475637a..924ef7448f 100644
--- a/src/main/java/com/iota/iri/service/milestone/impl/LatestMilestoneTrackerImpl.java
+++ b/src/main/java/com/iota/iri/service/milestone/impl/LatestMilestoneTrackerImpl.java
@@ -20,7 +20,6 @@
import java.util.Deque;
import java.util.HashSet;
import java.util.Set;
-import java.util.List;
import java.util.concurrent.TimeUnit;
/**
@@ -96,12 +95,12 @@ public class LatestMilestoneTrackerImpl implements LatestMilestoneTracker {
* A set that allows us to keep track of the candidates that have been seen and added to the {@link
* #milestoneCandidatesToAnalyze} already.
*/
- private final Set seenMilestoneCandidates = new HashSet<>();
+ private final Set seenMilestoneCandidates = new HashSet<>();
/**
* A list of milestones that still have to be analyzed.
*/
- private final Deque milestoneCandidatesToAnalyze = new ArrayDeque<>();
+ private final Deque milestoneCandidatesToAnalyze = new ArrayDeque<>();
/**
* A flag that allows us to detect if the background worker is in its first iteration (for different log
@@ -305,14 +304,13 @@ private void logProgress() {
*/
private void collectNewMilestoneCandidates() throws MilestoneException {
try {
- List transactions = AddressViewModel.loadAsSortedList(tangle, coordinatorAddress);
- for (TransactionViewModel tvm : transactions) {
+ for (Hash hash : AddressViewModel.load(tangle, coordinatorAddress).getHashes()) {
if (Thread.currentThread().isInterrupted()) {
return;
}
- if (tvm != null && seenMilestoneCandidates.add(tvm)) {
- milestoneCandidatesToAnalyze.addFirst(tvm);
+ if (seenMilestoneCandidates.add(hash)) {
+ milestoneCandidatesToAnalyze.addFirst(hash);
}
}
} catch (Exception e) {
@@ -340,9 +338,9 @@ private void analyzeMilestoneCandidates() throws MilestoneException {
return;
}
- TransactionViewModel candidateTransactionViewModel = milestoneCandidatesToAnalyze.pollFirst();
- if(!processMilestoneCandidate(candidateTransactionViewModel)) {
- seenMilestoneCandidates.remove(candidateTransactionViewModel);
+ Hash candidateTransactionHash = milestoneCandidatesToAnalyze.pollFirst();
+ if(!processMilestoneCandidate(candidateTransactionHash)) {
+ seenMilestoneCandidates.remove(candidateTransactionHash);
}
}
}
diff --git a/src/main/java/com/iota/iri/service/milestone/impl/MilestoneSolidifierImpl.java b/src/main/java/com/iota/iri/service/milestone/impl/MilestoneSolidifierImpl.java
index 9706f1f9df..289ad9b214 100644
--- a/src/main/java/com/iota/iri/service/milestone/impl/MilestoneSolidifierImpl.java
+++ b/src/main/java/com/iota/iri/service/milestone/impl/MilestoneSolidifierImpl.java
@@ -1,6 +1,6 @@
package com.iota.iri.service.milestone.impl;
-import com.iota.iri.TransactionValidator;
+import com.iota.iri.service.validation.TransactionSolidifier;
import com.iota.iri.model.Hash;
import com.iota.iri.service.milestone.MilestoneSolidifier;
import com.iota.iri.service.snapshot.SnapshotProvider;
@@ -35,7 +35,7 @@ public class MilestoneSolidifierImpl implements MilestoneSolidifier {
/**
* Defines the interval in which solidity checks are issued (in milliseconds).
*/
- private static final int SOLIDIFICATION_INTERVAL = 500;
+ private static final int SOLIDIFICATION_INTERVAL = 100;
/**
*
@@ -44,7 +44,7 @@ public class MilestoneSolidifierImpl implements MilestoneSolidifier {
*
*
* Note: We want to find the next previous milestone and not get stuck somewhere at the end of the tangle with a
- * long running {@link TransactionValidator#checkSolidity(Hash)} call.
+ * long running {@link TransactionSolidifier#checkSolidity(Hash)} call.
*
*/
private static final int SOLIDIFICATION_TRANSACTIONS_LIMIT = 50000;
@@ -60,9 +60,9 @@ public class MilestoneSolidifierImpl implements MilestoneSolidifier {
private final SnapshotProvider snapshotProvider;
/**
- * Holds a reference to the TransactionValidator which allows us to issue solidity checks.
+ * Holds a reference to the transactionSolidifier which allows us to issue solidity checks.
*/
- private final TransactionValidator transactionValidator;
+ private final TransactionSolidifier transactionSolidifier;
/**
* Holds a reference to the manager of the background worker.
@@ -105,11 +105,11 @@ public class MilestoneSolidifierImpl implements MilestoneSolidifier {
/**
* @param snapshotProvider snapshot provider which gives us access to the relevant snapshots
- * @param transactionValidator TransactionValidator instance that is used by the node
+ * @param transactionSolidifier transactionSolidifier instance that is used by the node
*/
- public MilestoneSolidifierImpl(SnapshotProvider snapshotProvider, TransactionValidator transactionValidator) {
+ public MilestoneSolidifierImpl(SnapshotProvider snapshotProvider, TransactionSolidifier transactionSolidifier) {
this.snapshotProvider = snapshotProvider;
- this.transactionValidator = transactionValidator;
+ this.transactionSolidifier = transactionSolidifier;
}
/**
@@ -321,7 +321,7 @@ private Map.Entry getNextSolidificationCandidate() {
*
*
* It first dumps a log message to keep the node operator informed about the progress of solidification, and then
- * issues the {@link TransactionValidator#checkSolidity(Hash, int)} call that starts the solidification
+ * issues the {@link TransactionSolidifier#checkSolidity(Hash, int)} call that starts the solidification
* process.
*
*
@@ -341,7 +341,7 @@ private boolean isSolid(Map.Entry currentEntry) {
}
try {
- return transactionValidator.checkSolidity(currentEntry.getKey(), SOLIDIFICATION_TRANSACTIONS_LIMIT);
+ return transactionSolidifier.addMilestoneToSolidificationQueue(currentEntry.getKey(), SOLIDIFICATION_TRANSACTIONS_LIMIT);
} catch (Exception e) {
log.error("Error while solidifying milestone #" + currentEntry.getValue(), e);
diff --git a/src/main/java/com/iota/iri/service/validation/TransactionSolidifier.java b/src/main/java/com/iota/iri/service/validation/TransactionSolidifier.java
new file mode 100644
index 0000000000..3e2ed5bec5
--- /dev/null
+++ b/src/main/java/com/iota/iri/service/validation/TransactionSolidifier.java
@@ -0,0 +1,126 @@
+package com.iota.iri.service.validation;
+
+import com.iota.iri.controllers.TipsViewModel;
+import com.iota.iri.controllers.TransactionViewModel;
+import com.iota.iri.model.Hash;
+import com.iota.iri.service.validation.impl.TransactionSolidifierImpl;
+import com.iota.iri.network.TransactionRequester;
+
+import java.util.Set;
+
+/**
+ * Solidification tool. Transactions placed into the solidification queue will be checked for solidity. Any missing
+ * reference transactions will be placed into the {@link TransactionRequester}. If a transaction is found to be solid
+ * it is updated as such and placed into the BroadcastQueue to be sent off to the node's neighbours.
+ */
+public interface TransactionSolidifier {
+
+ /**
+ * Initialize the executor service. Start processing transactions to solidify.
+ */
+ void start();
+
+ /**
+ * Interrupt thread processes and shut down the executor service.
+ */
+ void shutdown();
+
+ /**
+ * Add a hash to the solidification queue, and runs an initial {@link #checkSolidity} call.
+ *
+ * @param hash Hash of the transaction to solidify
+ */
+ void addToSolidificationQueue(Hash hash);
+
+ /**
+ * Checks if milestone transaction is solid. Returns true if it is, and if it is not, it adds the hash to the
+ * solidification queue and returns false.
+ *
+ * @param hash Hash of the transaction to solidify
+ * @param maxToProcess Maximum number of transactions to analyze
+ * @return True if solid, false if not
+ */
+ boolean addMilestoneToSolidificationQueue(Hash hash, int maxToProcess);
+ /**
+ * Fetch a copy of the current transactionsToBroadcast set.
+ * @return A set of {@link TransactionViewModel} objects to be broadcast.
+ */
+ Set getBroadcastQueue();
+
+ /**
+ * Remove any broadcasted transactions from the transactionsToBroadcast set
+ * @param transactionsBroadcasted A set of {@link TransactionViewModel} objects to remove from the set.
+ */
+ void clearFromBroadcastQueue(Set transactionsBroadcasted);
+
+ /**
+ * This method does the same as {@link #checkSolidity(Hash, int)} but defaults to an unlimited amount
+ * of transactions that are allowed to be traversed.
+ *
+ * @param hash hash of the transactions that shall get checked
+ * @return true if the transaction is solid and false otherwise
+ * @throws Exception if anything goes wrong while trying to solidify the transaction
+ */
+ boolean checkSolidity(Hash hash) throws Exception;
+
+ /**
+ * This method checks transactions for solidity and marks them accordingly if they are found to be solid.
+ *
+ * It iterates through all approved transactions until it finds one that is missing in the database or until it
+ * reached solid transactions on all traversed subtangles. In case of a missing transactions it issues a transaction
+ * request and returns false. If no missing transaction is found, it marks the processed transactions as solid in
+ * the database and returns true.
+ *
+ * Since this operation can potentially take a long time to terminate if it would have to traverse big parts of the
+ * tangle, it is possible to limit the amount of transactions that are allowed to be processed, while looking for
+ * unsolid / missing approvees. This can be useful when trying to "interrupt" the solidification of one transaction
+ * (if it takes too many steps) to give another one the chance to be solidified instead (i.e. prevent blocks in the
+ * solidification threads).
+ *
+ * @param hash hash of the transactions that shall get checked
+ * @param maxProcessedTransactions the maximum amount of transactions that are allowed to be traversed
+ * @return true if the transaction is solid and false otherwise
+ * @throws Exception if anything goes wrong while trying to solidify the transaction
+ */
+ boolean checkSolidity(Hash hash, int maxProcessedTransactions) throws Exception;
+
+ /**
+ * Updates a transaction after it was stored in the tangle. Tells the node to not request the transaction anymore,
+ * to update the live tips accordingly, and attempts to quickly solidify the transaction.
+ *
+ *
+ * Performs the following operations:
+ *
+ *
+ * - Removes {@code transactionViewModel}'s hash from the the request queue since we already found it.
+ * - If {@code transactionViewModel} has no children (approvers), we add it to the node's active tip list.
+ * - Removes {@code transactionViewModel}'s parents (branch & trunk) from the node's tip list
+ * (if they're present there).
+ * - Attempts to quickly solidify {@code transactionViewModel} by checking whether its direct parents
+ * are solid. If solid we add it to the queue transaction solidification thread to help it propagate the
+ * solidification to the approving child transactions.
+ * - Requests missing direct parent (trunk & branch) transactions that are needed to solidify
+ * {@code transactionViewModel}.
+ *
+ * @param transactionViewModel received transaction that is being updated
+ * @throws Exception if an error occurred while trying to solidify
+ * @see TipsViewModel
+ */
+ void updateStatus(TransactionViewModel transactionViewModel) throws Exception;
+
+ /**
+ * Tries to solidify the transactions quickly by performing {@link TransactionSolidifierImpl#checkApproovee} on
+ * both parents (trunk and branch). If the parents are solid, mark the transactions as solid.
+ * @param transactionViewModel transaction to solidify
+ * @return true if we made the transaction solid, else false.
+ * @throws Exception
+ */
+ boolean quickSetSolid(TransactionViewModel transactionViewModel) throws Exception;
+
+ /**
+ * Add to the propagation queue where it will be processed to help solidify approving transactions faster
+ * @param hash The transaction hash to be removed
+ * @throws Exception
+ */
+ void addToPropagationQueue(Hash hash) throws Exception;
+}
diff --git a/src/main/java/com/iota/iri/service/validation/TransactionValidator.java b/src/main/java/com/iota/iri/service/validation/TransactionValidator.java
new file mode 100644
index 0000000000..dd4ea75028
--- /dev/null
+++ b/src/main/java/com/iota/iri/service/validation/TransactionValidator.java
@@ -0,0 +1,170 @@
+package com.iota.iri.service.validation;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.iota.iri.conf.ProtocolConfig;
+import com.iota.iri.controllers.TransactionViewModel;
+import com.iota.iri.crypto.Curl;
+import com.iota.iri.crypto.Sponge;
+import com.iota.iri.crypto.SpongeFactory;
+import com.iota.iri.model.TransactionHash;
+import com.iota.iri.network.TransactionRequester;
+import com.iota.iri.service.snapshot.SnapshotProvider;
+
+/**
+ * Tool for determining validity of a transaction via a {@link TransactionViewModel}, tryte array or byte array.
+ */
+public class TransactionValidator {
+ private static final int TESTNET_MWM_CAP = 13;
+
+ private final SnapshotProvider snapshotProvider;
+ private final TransactionRequester transactionRequester;
+ private int minWeightMagnitude = 81;
+ private static final long MAX_TIMESTAMP_FUTURE = 2L * 60L * 60L;
+ private static final long MAX_TIMESTAMP_FUTURE_MS = MAX_TIMESTAMP_FUTURE * 1_000L;
+
+
+ /**
+ * Constructor for Tangle Validator
+ *
+ * @param snapshotProvider data provider for the snapshots that are relevant for the node
+ * @param transactionRequester used to request missing transactions from neighbors
+ * @param protocolConfig used for checking if we are in testnet and mwm. testnet true if we are in testnet
+ * mode, this caps {@code mwm} to {@value #TESTNET_MWM_CAP} regardless of parameter input.
+ * minimum weight magnitude: the minimal number of 9s that ought to appear at the end of the
+ * transaction hash
+ */
+ public TransactionValidator(SnapshotProvider snapshotProvider, TransactionRequester transactionRequester, ProtocolConfig protocolConfig) {
+ this.snapshotProvider = snapshotProvider;
+ this.transactionRequester = transactionRequester;
+ setMwm(protocolConfig.isTestnet(), protocolConfig.getMwm());
+ }
+
+ /**
+ * Set the Minimum Weight Magnitude for validation checks.
+ */
+ @VisibleForTesting
+ void setMwm(boolean testnet, int mwm) {
+ minWeightMagnitude = mwm;
+
+ //lowest allowed MWM encoded in 46 bytes.
+ if (!testnet){
+ minWeightMagnitude = Math.max(minWeightMagnitude, TESTNET_MWM_CAP);
+ }
+ }
+
+ /**
+ * @return the minimal number of trailing 9s that have to be present at the end of the transaction hash
+ * in order to validate that sufficient proof of work has been done
+ */
+ public int getMinWeightMagnitude() {
+ return minWeightMagnitude;
+ }
+
+ /**
+ * Checks that the timestamp of the transaction is below the last global snapshot time
+ * or more than {@value #MAX_TIMESTAMP_FUTURE} seconds in the future, and thus invalid.
+ *
+ *
+ * First the attachment timestamp (set after performing POW) is checked, and if not available
+ * the regular timestamp is checked. Genesis transaction will always be valid.
+ *
+ * @param transactionViewModel transaction under test
+ * @return true if timestamp is not in valid bounds and {@code transactionViewModel} is not genesis.
+ * Else returns false.
+ */
+ private boolean hasInvalidTimestamp(TransactionViewModel transactionViewModel) {
+ // ignore invalid timestamps for transactions that were requested by our node while solidifying a milestone
+ if(transactionRequester.wasTransactionRecentlyRequested(transactionViewModel.getHash())) {
+ return false;
+ }
+
+ if (transactionViewModel.getAttachmentTimestamp() == 0) {
+ return transactionViewModel.getTimestamp() < snapshotProvider.getInitialSnapshot().getTimestamp() && !snapshotProvider.getInitialSnapshot().hasSolidEntryPoint(transactionViewModel.getHash())
+ || transactionViewModel.getTimestamp() > (System.currentTimeMillis() / 1000) + MAX_TIMESTAMP_FUTURE;
+ }
+ return transactionViewModel.getAttachmentTimestamp() < (snapshotProvider.getInitialSnapshot().getTimestamp() * 1000L)
+ || transactionViewModel.getAttachmentTimestamp() > System.currentTimeMillis() + MAX_TIMESTAMP_FUTURE_MS;
+ }
+
+ /**
+ * Runs the following validation checks on a transaction:
+ *
+ * - {@link #hasInvalidTimestamp} check.
+ * - Check that no value trits are set beyond the usable index, otherwise we will have values larger
+ * than max supply.
+ * - Check that sufficient POW was performed.
+ * - In value transactions, we check that the address has 0 set as the last trit. This must be because of the
+ * conversion between bytes to trits.
+ *
+ *Exception is thrown upon failure.
+ *
+ * @param transactionViewModel transaction that should be validated
+ * @param minWeightMagnitude the minimal number of trailing 9s at the end of the transaction hash
+ * @throws StaleTimestampException if timestamp check fails
+ * @throws IllegalStateException if any of the other checks fail
+ */
+ public void runValidation(TransactionViewModel transactionViewModel, final int minWeightMagnitude) {
+ transactionViewModel.setMetadata();
+ transactionViewModel.setAttachmentData();
+ if (hasInvalidTimestamp(transactionViewModel)) {
+ throw new StaleTimestampException("Invalid transaction timestamp.");
+ }
+
+ confirmValidTransactionValues(transactionViewModel);
+ int weightMagnitude = transactionViewModel.weightMagnitude;
+ if (weightMagnitude < minWeightMagnitude) {
+ throw new IllegalStateException("Invalid transaction hash");
+ }
+
+ if (transactionViewModel.value() != 0 && transactionViewModel.getAddressHash().trits()[Curl.HASH_LENGTH - 1] != 0) {
+ throw new IllegalStateException("Invalid transaction address");
+ }
+ }
+
+ private void confirmValidTransactionValues(TransactionViewModel transactionViewModel) throws IllegalStateException {
+ for (int i = TransactionViewModel.VALUE_TRINARY_OFFSET + TransactionViewModel.VALUE_USABLE_TRINARY_SIZE;
+ i < TransactionViewModel.VALUE_TRINARY_OFFSET + TransactionViewModel.VALUE_TRINARY_SIZE; i++) {
+ if (transactionViewModel.trits()[i] != 0) {
+ throw new IllegalStateException("Invalid transaction value");
+ }
+ }
+ }
+
+ /**
+ * Creates a new transaction from {@code trits} and validates it with {@link #runValidation}.
+ *
+ * @param trits raw transaction trits
+ * @param minWeightMagnitude minimal number of trailing 9s in transaction for POW validation
+ * @return the transaction resulting from the raw trits if valid.
+ * @throws RuntimeException if validation fails
+ */
+ public TransactionViewModel validateTrits(final byte[] trits, int minWeightMagnitude) {
+ TransactionViewModel transactionViewModel = new TransactionViewModel(trits, TransactionHash.calculate(trits, 0, trits.length, SpongeFactory.create(SpongeFactory.Mode.CURLP81)));
+ runValidation(transactionViewModel, minWeightMagnitude);
+ return transactionViewModel;
+ }
+
+ /**
+ * Creates a new transaction from {@code bytes} and validates it with {@link #runValidation}.
+ *
+ * @param bytes raw transaction bytes
+ * @param minWeightMagnitude minimal number of trailing 9s in transaction for POW validation
+ * @return the transaction resulting from the raw bytes if valid
+ * @throws RuntimeException if validation fails
+ */
+ public TransactionViewModel validateBytes(final byte[] bytes, int minWeightMagnitude, Sponge curl) {
+ TransactionViewModel transactionViewModel = new TransactionViewModel(bytes, TransactionHash.calculate(bytes,
+ TransactionViewModel.TRINARY_SIZE, curl));
+ runValidation(transactionViewModel, minWeightMagnitude);
+ return transactionViewModel;
+ }
+
+ /**
+ * Thrown if transaction fails {@link #hasInvalidTimestamp} check.
+ */
+ public static class StaleTimestampException extends RuntimeException {
+ StaleTimestampException (String message) {
+ super(message);
+ }
+ }
+}
diff --git a/src/main/java/com/iota/iri/service/validation/impl/TransactionSolidifierImpl.java b/src/main/java/com/iota/iri/service/validation/impl/TransactionSolidifierImpl.java
new file mode 100644
index 0000000000..7d1cc49a49
--- /dev/null
+++ b/src/main/java/com/iota/iri/service/validation/impl/TransactionSolidifierImpl.java
@@ -0,0 +1,383 @@
+package com.iota.iri.service.validation.impl;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.iota.iri.controllers.TipsViewModel;
+import com.iota.iri.controllers.TransactionViewModel;
+import com.iota.iri.model.Hash;
+import com.iota.iri.network.pipeline.TransactionProcessingPipeline;
+import com.iota.iri.network.TransactionRequester;
+import com.iota.iri.service.snapshot.SnapshotProvider;
+import com.iota.iri.service.validation.TransactionSolidifier;
+import com.iota.iri.storage.Tangle;
+import com.iota.iri.utils.log.interval.IntervalLogger;
+import com.iota.iri.utils.thread.DedicatedScheduledExecutorService;
+import com.iota.iri.utils.thread.SilentScheduledExecutorService;
+
+import java.util.*;
+import java.util.concurrent.*;
+
+import static com.iota.iri.controllers.TransactionViewModel.PREFILLED_SLOT;
+import static com.iota.iri.controllers.TransactionViewModel.fromHash;
+
+/**
+ * A solidifier class for processing transactions. Transactions are checked for solidity, and missing transactions are
+ * subsequently requested. Once a transaction is solidified correctly it is placed into a broadcasting set to be sent to
+ * neighboring nodes.
+ */
+public class TransactionSolidifierImpl implements TransactionSolidifier {
+
+ private Tangle tangle;
+ private SnapshotProvider snapshotProvider;
+ private TransactionRequester transactionRequester;
+
+ /**
+ * Max size for all queues.
+ */
+ private static final int MAX_SIZE= 10000;
+
+ private static final int SOLIDIFICATION_INTERVAL = 100;
+
+ private static final IntervalLogger log = new IntervalLogger(TransactionSolidifier.class);
+
+ /**
+ * Executor service for running the {@link #processTransactionsToSolidify()}.
+ */
+ private SilentScheduledExecutorService executorService = new DedicatedScheduledExecutorService(
+ "Transaction Solidifier", log.delegate());
+
+ /**
+ * A queue for processing transactions with the {@link #checkSolidity(Hash)} call. Once a transaction has been
+ * marked solid it will be placed into the {@link #transactionsToBroadcast} queue.
+ */
+ private BlockingQueue transactionsToSolidify = new ArrayBlockingQueue<>(MAX_SIZE);
+
+ /**
+ * A queue for processing transactions with the {@link #propagateSolidTransactions()} call. This will check
+ * approving transactions with {@link #quickSetSolid(TransactionViewModel)}.
+ */
+ private BlockingQueue solidTransactions = new ArrayBlockingQueue<>(MAX_SIZE);
+
+ /**
+ * A set of transactions that will be called by the {@link TransactionProcessingPipeline} to be broadcast to
+ * neighboring nodes.
+ */
+ private BlockingQueue transactionsToBroadcast = new ArrayBlockingQueue<>(MAX_SIZE);
+
+ private TipsViewModel tipsViewModel;
+
+ /**
+ * Constructor for the solidifier.
+ * @param tangle The DB reference
+ * @param snapshotProvider For fetching entry points for solidity checks
+ * @param transactionRequester A requester for missing transactions
+ */
+ public TransactionSolidifierImpl(Tangle tangle, SnapshotProvider snapshotProvider, TransactionRequester transactionRequester,
+ TipsViewModel tipsViewModel){
+ this.tangle = tangle;
+ this.snapshotProvider = snapshotProvider;
+ this.transactionRequester = transactionRequester;
+ this.tipsViewModel = tipsViewModel;
+ }
+
+ /**
+ *{@inheritDoc}
+ */
+ @Override
+ public void start(){
+ executorService.silentScheduleWithFixedDelay(this::processTransactionsToSolidify, 0,
+ SOLIDIFICATION_INTERVAL, TimeUnit.MILLISECONDS);
+ }
+
+ /**
+ *{@inheritDoc}
+ */
+ @Override
+ public void shutdown() {
+ executorService.shutdownNow();
+ }
+
+ /**
+ *{@inheritDoc}
+ */
+ @Override
+ public void addToSolidificationQueue(Hash hash){
+ try{
+ if(!transactionsToSolidify.contains(hash)) {
+ if (transactionsToSolidify.size() >= MAX_SIZE - 1) {
+ transactionsToSolidify.remove();
+ }
+
+ transactionsToSolidify.put(hash);
+ }
+ } catch(Exception e){
+ log.error("Error placing transaction into solidification queue",e);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean addMilestoneToSolidificationQueue(Hash hash, int maxToProcess){
+ try{
+ TransactionViewModel tx = fromHash(tangle, hash);
+ if(tx.isSolid()){
+ addToPropagationQueue(hash);
+ return true;
+ }
+ addToSolidificationQueue(hash);
+ return false;
+ }catch(Exception e){
+ log.error("Error adding milestone to solidification queue", e);
+ return false;
+ }
+ }
+
+ /**
+ *{@inheritDoc}
+ */
+ @Override
+ public Set getBroadcastQueue(){
+ return new LinkedHashSet<>(transactionsToBroadcast);
+ }
+
+ /**
+ *{@inheritDoc}
+ */
+ @Override
+ public void clearFromBroadcastQueue(Set transactionsBroadcasted){
+ for (TransactionViewModel tvm : transactionsBroadcasted) {
+ transactionsToBroadcast.remove(tvm);
+ }
+ }
+
+
+ /**
+ * Iterate through the {@link #transactionsToSolidify} queue and call {@link #checkSolidity(Hash)} on each hash.
+ * Solid transactions are then processed into the {@link #transactionsToBroadcast} queue.
+ */
+ private void processTransactionsToSolidify(){
+ Hash hash;
+ if((hash = transactionsToSolidify.poll()) != null) {
+ try {
+ checkSolidity(hash);
+ } catch (Exception e) {
+ log.info(e.getMessage());
+ }
+ }
+ propagateSolidTransactions();
+ }
+
+ /**
+ *{@inheritDoc}
+ */
+ @Override
+ public boolean checkSolidity(Hash hash) throws Exception {
+ return checkSolidity(hash, 50000);
+ }
+
+ /**
+ *{@inheritDoc}
+ */
+ @Override
+ public boolean checkSolidity(Hash hash, int maxProcessedTransactions) throws Exception {
+ if(fromHash(tangle, hash).isSolid()) {
+ return true;
+ }
+ LinkedHashSet analyzedHashes = new LinkedHashSet<>(snapshotProvider.getInitialSnapshot().getSolidEntryPoints().keySet());
+ if(maxProcessedTransactions != Integer.MAX_VALUE) {
+ maxProcessedTransactions += analyzedHashes.size();
+ }
+ boolean solid = true;
+ final Queue nonAnalyzedTransactions = new LinkedList<>(Collections.singleton(hash));
+ Hash hashPointer;
+ while ((hashPointer = nonAnalyzedTransactions.poll()) != null) {
+ if (!analyzedHashes.add(hashPointer)) {
+ continue;
+ }
+
+ if (analyzedHashes.size() >= maxProcessedTransactions) {
+ return false;
+ }
+
+ TransactionViewModel transaction = fromHash(tangle, hashPointer);
+ if (isUnsolidWithoutEntryPoint(transaction, hashPointer)) {
+ if (transaction.getType() == PREFILLED_SLOT) {
+ solid = false;
+ checkRequester(hashPointer);
+ } else {
+ nonAnalyzedTransactions.offer(transaction.getTrunkTransactionHash());
+ nonAnalyzedTransactions.offer(transaction.getBranchTransactionHash());
+ }
+ }
+ }
+ if (solid) {
+ updateTransactions(analyzedHashes);
+ }
+ analyzedHashes.clear();
+ return solid;
+ }
+
+
+ /**
+ * Check if a transaction is present in the {@link #transactionRequester}, if not, it is added.
+ * @param hashPointer The hash of the transaction to request
+ */
+ private void checkRequester(Hash hashPointer){
+ if (!transactionRequester.isTransactionRequested(hashPointer)) {
+ transactionRequester.requestTransaction(hashPointer);
+ }
+ }
+
+ /**
+ * Iterate through analyzed hashes and place them in the {@link #transactionsToBroadcast} queue
+ * @param hashes Analyzed hashes from the {@link #checkSolidity(Hash)} call
+ */
+ private void updateTransactions(Set hashes) {
+ hashes.forEach(hash -> {
+ try {
+ TransactionViewModel tvm = fromHash(tangle, hash);
+ tvm.updateHeights(tangle, snapshotProvider.getInitialSnapshot());
+
+ if(!tvm.isSolid()){
+ tvm.updateSolid(true);
+ tvm.update(tangle, snapshotProvider.getInitialSnapshot(), "solid|height");
+ }
+ addToBroadcastQueue(tvm);
+ addToPropagationQueue(tvm.getHash());
+ } catch (Exception e) {
+ log.info(e.getMessage());
+ }
+ });
+ }
+
+ /**
+ * Returns true if transaction is not solid and there are no solid entry points from the initial snapshot.
+ */
+ private boolean isUnsolidWithoutEntryPoint(TransactionViewModel transaction, Hash hashPointer) throws Exception{
+ if(!transaction.isSolid() && !snapshotProvider.getInitialSnapshot().hasSolidEntryPoint(hashPointer)){
+ return true;
+ }
+ addToPropagationQueue(hashPointer);
+ return false;
+ }
+
+
+ private void addToBroadcastQueue(TransactionViewModel tvm) {
+ try {
+ if (transactionsToBroadcast.size() >= MAX_SIZE) {
+ transactionsToBroadcast.remove();
+ }
+
+ transactionsToBroadcast.put(tvm);
+ } catch(Exception e){
+ log.info("Error placing transaction into broadcast queue: " + e.getMessage());
+ }
+ }
+
+ @VisibleForTesting
+ Set getSolidificationQueue(){
+ return new LinkedHashSet<>(transactionsToSolidify);
+ }
+
+
+ @Override
+ public void updateStatus(TransactionViewModel transactionViewModel) throws Exception {
+ transactionRequester.clearTransactionRequest(transactionViewModel.getHash());
+ if(transactionViewModel.getApprovers(tangle).size() == 0) {
+ tipsViewModel.addTipHash(transactionViewModel.getHash());
+ }
+ tipsViewModel.removeTipHash(transactionViewModel.getTrunkTransactionHash());
+ tipsViewModel.removeTipHash(transactionViewModel.getBranchTransactionHash());
+
+ if(quickSetSolid(transactionViewModel)) {
+ transactionViewModel.update(tangle, snapshotProvider.getInitialSnapshot(), "solid|height");
+ tipsViewModel.setSolid(transactionViewModel.getHash());
+ addToPropagationQueue(transactionViewModel.getHash());
+ }
+ }
+
+ @Override
+ public void addToPropagationQueue(Hash hash) throws Exception{
+ if(!solidTransactions.contains(hash)) {
+ if (solidTransactions.size() >= MAX_SIZE) {
+ solidTransactions.poll();
+ }
+ solidTransactions.put(hash);
+ }
+ }
+
+ @Override
+ public boolean quickSetSolid(final TransactionViewModel transactionViewModel) throws Exception {
+ if(!transactionViewModel.isSolid()) {
+ boolean solid = true;
+ if (!checkApproovee(transactionViewModel.getTrunkTransaction(tangle))) {
+ solid = false;
+ }
+ if (!checkApproovee(transactionViewModel.getBranchTransaction(tangle))) {
+ solid = false;
+ }
+ if(solid) {
+ transactionViewModel.updateSolid(true);
+ transactionViewModel.updateHeights(tangle, snapshotProvider.getInitialSnapshot());
+ addToPropagationQueue(transactionViewModel.getHash());
+ addToBroadcastQueue(transactionViewModel);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * If the the {@code approvee} is missing, request it from a neighbor.
+ * @param approovee transaction we check.
+ * @return true if {@code approvee} is solid.
+ * @throws Exception if we encounter an error while requesting a transaction
+ */
+ private boolean checkApproovee(TransactionViewModel approovee) throws Exception {
+ if(snapshotProvider.getInitialSnapshot().hasSolidEntryPoint(approovee.getHash())) {
+ return true;
+ }
+ if(approovee.getType() == PREFILLED_SLOT) {
+ // don't solidify from the bottom until cuckoo filters can identify where we deleted -> otherwise we will
+ // continue requesting old transactions forever
+ //transactionRequester.requestTransaction(approovee.getHash(), false);
+ return false;
+ }
+ return approovee.isSolid();
+ }
+
+ @VisibleForTesting
+ void propagateSolidTransactions() {
+ while(!Thread.currentThread().isInterrupted() && solidTransactions.peek() != null) {
+ try {
+ Hash hash = solidTransactions.poll();
+ TransactionViewModel transaction = fromHash(tangle, hash);
+ Set approvers = transaction.getApprovers(tangle).getHashes();
+ for(Hash h: approvers) {
+ TransactionViewModel tx = fromHash(tangle, h);
+ if (quietQuickSetSolid(tx)) {
+ tx.update(tangle, snapshotProvider.getInitialSnapshot(), "solid|height");
+ tipsViewModel.setSolid(h);
+ }
+ }
+ } catch (Exception e) {
+ log.error("Error while propagating solidity upwards", e);
+ }
+ }
+ }
+
+ /**
+ * Perform a {@link #quickSetSolid} while capturing and logging errors
+ * @param transactionViewModel transaction we try to solidify.
+ * @return true if we managed to solidify, else false.
+ */
+ private boolean quietQuickSetSolid(TransactionViewModel transactionViewModel) {
+ try {
+ return quickSetSolid(transactionViewModel);
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ return false;
+ }
+ }
+}
diff --git a/src/main/java/com/iota/iri/utils/dag/DAGHelper.java b/src/main/java/com/iota/iri/utils/dag/DAGHelper.java
index 37459f231c..d529f3456b 100644
--- a/src/main/java/com/iota/iri/utils/dag/DAGHelper.java
+++ b/src/main/java/com/iota/iri/utils/dag/DAGHelper.java
@@ -94,7 +94,8 @@ public void traverseApprovers(Hash startingTransactionHash,
try {
Hash currentTransactionHash;
while((currentTransactionHash = transactionsToExamine.poll()) != null) {
- if(currentTransactionHash == startingTransactionHash || processedTransactions.add(currentTransactionHash)) {
+ if(currentTransactionHash == startingTransactionHash || processedTransactions == null ||
+ processedTransactions.add(currentTransactionHash)) {
TransactionViewModel currentTransaction = TransactionViewModel.fromHash(tangle, currentTransactionHash);
if(
// do not "test" the starting transaction since it is not an "approver"
@@ -108,7 +109,11 @@ public void traverseApprovers(Hash startingTransactionHash,
currentTransactionConsumer.accept(currentTransaction);
}
- transactionsToExamine.addAll(ApproveeViewModel.load(tangle, currentTransactionHash).getHashes());
+ ApproveeViewModel.load(tangle, currentTransactionHash).getHashes().forEach(hash -> {
+ if(!transactionsToExamine.contains(hash)){
+ transactionsToExamine.add(hash);
+ }
+ });
}
}
}
@@ -132,7 +137,7 @@ public void traverseApprovers(Hash startingTransactionHash,
public void traverseApprovers(Hash startingTransactionHash,
Predicate condition,
Consumer currentTransactionConsumer) throws TraversalException {
- traverseApprovers(startingTransactionHash, condition, currentTransactionConsumer, new HashSet<>());
+ traverseApprovers(startingTransactionHash, condition, currentTransactionConsumer, null);
}
//endregion ////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -169,7 +174,8 @@ public void traverseApprovees(Hash startingTransactionHash,
try {
Hash currentTransactionHash;
while((currentTransactionHash = transactionsToExamine.poll()) != null) {
- if(currentTransactionHash == startingTransactionHash || processedTransactions.add(currentTransactionHash)) {
+ if(currentTransactionHash == startingTransactionHash || processedTransactions == null ||
+ processedTransactions.add(currentTransactionHash)) {
TransactionViewModel currentTransaction = TransactionViewModel.fromHash(tangle, currentTransactionHash);
if(
currentTransaction.getType() != TransactionViewModel.PREFILLED_SLOT &&(
@@ -183,8 +189,13 @@ public void traverseApprovees(Hash startingTransactionHash,
currentTransactionConsumer.accept(currentTransaction);
}
- transactionsToExamine.add(currentTransaction.getBranchTransactionHash());
- transactionsToExamine.add(currentTransaction.getTrunkTransactionHash());
+ if(!transactionsToExamine.contains(currentTransaction.getBranchTransactionHash())) {
+ transactionsToExamine.add(currentTransaction.getBranchTransactionHash());
+ }
+
+ if(!transactionsToExamine.contains(currentTransaction.getTrunkTransactionHash())) {
+ transactionsToExamine.add(currentTransaction.getTrunkTransactionHash());
+ }
}
}
}
@@ -209,7 +220,7 @@ public void traverseApprovees(Hash startingTransactionHash,
Predicate condition,
ThrowingConsumer currentTransactionConsumer)
throws TraversalException {
- traverseApprovees(startingTransactionHash, condition, currentTransactionConsumer, new HashSet<>());
+ traverseApprovees(startingTransactionHash, condition, currentTransactionConsumer, null);
}
/**
diff --git a/src/test/java/com/iota/iri/MainInjectionConfigurationTest.java b/src/test/java/com/iota/iri/MainInjectionConfigurationTest.java
index 3695f6cdba..be16d5df2f 100644
--- a/src/test/java/com/iota/iri/MainInjectionConfigurationTest.java
+++ b/src/test/java/com/iota/iri/MainInjectionConfigurationTest.java
@@ -23,6 +23,7 @@
import com.iota.iri.service.spentaddresses.SpentAddressesProvider;
import com.iota.iri.service.spentaddresses.SpentAddressesService;
import com.iota.iri.service.transactionpruning.TransactionPruner;
+import com.iota.iri.service.validation.TransactionValidator;
import com.iota.iri.storage.LocalSnapshotsPersistenceProvider;
import com.iota.iri.storage.Tangle;
import org.junit.Test;
diff --git a/src/test/java/com/iota/iri/TransactionValidatorTest.java b/src/test/java/com/iota/iri/TransactionValidatorTest.java
deleted file mode 100644
index ad35624978..0000000000
--- a/src/test/java/com/iota/iri/TransactionValidatorTest.java
+++ /dev/null
@@ -1,224 +0,0 @@
-package com.iota.iri;
-
-import com.iota.iri.conf.MainnetConfig;
-import com.iota.iri.conf.ProtocolConfig;
-
-import com.iota.iri.controllers.TipsViewModel;
-import com.iota.iri.controllers.TransactionViewModel;
-import com.iota.iri.crypto.SpongeFactory;
-import com.iota.iri.model.TransactionHash;
-import com.iota.iri.network.TransactionRequester;
-import com.iota.iri.service.snapshot.SnapshotProvider;
-
-import com.iota.iri.service.snapshot.impl.SnapshotMockUtils;
-import com.iota.iri.storage.Tangle;
-import com.iota.iri.storage.rocksDB.RocksDBPersistenceProvider;
-import com.iota.iri.utils.Converter;
-
-import org.junit.*;
-import org.junit.rules.TemporaryFolder;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-import static com.iota.iri.TransactionTestUtils.getTransactionHash;
-import static com.iota.iri.TransactionTestUtils.getTransactionTrits;
-import static com.iota.iri.TransactionTestUtils.getTransactionTritsWithTrunkAndBranch;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class TransactionValidatorTest {
-
- private static final int MAINNET_MWM = 14;
- private static final TemporaryFolder dbFolder = new TemporaryFolder();
- private static final TemporaryFolder logFolder = new TemporaryFolder();
- private static Tangle tangle;
- private static TransactionValidator txValidator;
-
- @Rule
- public MockitoRule mockitoRule = MockitoJUnit.rule();
-
- @Mock
- private static SnapshotProvider snapshotProvider;
-
- @BeforeClass
- public static void setUp() throws Exception {
- dbFolder.create();
- logFolder.create();
- tangle = new Tangle();
- tangle.addPersistenceProvider(
- new RocksDBPersistenceProvider(
- dbFolder.getRoot().getAbsolutePath(), logFolder.getRoot().getAbsolutePath(),1000, Tangle.COLUMN_FAMILIES, Tangle.METADATA_COLUMN_FAMILY));
- tangle.init();
- }
-
- @AfterClass
- public static void tearDown() throws Exception {
- tangle.shutdown();
- dbFolder.delete();
- logFolder.delete();
- }
-
- @Before
- public void setUpEach() {
- when(snapshotProvider.getInitialSnapshot()).thenReturn(SnapshotMockUtils.createSnapshot());
- TipsViewModel tipsViewModel = new TipsViewModel();
- TransactionRequester txRequester = new TransactionRequester(tangle, snapshotProvider);
- txValidator = new TransactionValidator(tangle, snapshotProvider, tipsViewModel, txRequester, new MainnetConfig());
- txValidator.setMwm(false, MAINNET_MWM);
- }
-
- @Test
- public void testMinMwm() {
- ProtocolConfig protocolConfig = mock(ProtocolConfig.class);
- when(protocolConfig.getMwm()).thenReturn(5);
- TransactionValidator transactionValidator = new TransactionValidator(null, null, null, null, protocolConfig);
- assertEquals("Expected testnet minimum minWeightMagnitude", 13, transactionValidator.getMinWeightMagnitude());
- }
-
- @Test
- public void validateTrits() {
- byte[] trits = getTransactionTrits();
- Converter.copyTrits(0, trits, 0, trits.length);
- txValidator.validateTrits(trits, MAINNET_MWM);
- }
-
- @Test(expected = RuntimeException.class)
- public void validateTritsWithInvalidMetadata() {
- byte[] trits = getTransactionTrits();
- txValidator.validateTrits(trits, MAINNET_MWM);
- }
-
- @Test
- public void validateBytesWithNewCurl() {
- byte[] trits = getTransactionTrits();
- Converter.copyTrits(0, trits, 0, trits.length);
- byte[] bytes = Converter.allocateBytesForTrits(trits.length);
- Converter.bytes(trits, 0, bytes, 0, trits.length);
- txValidator.validateBytes(bytes, txValidator.getMinWeightMagnitude(), SpongeFactory.create(SpongeFactory.Mode.CURLP81));
- }
-
- @Test
- public void verifyTxIsSolid() throws Exception {
- TransactionViewModel tx = getTxWithBranchAndTrunk();
- assertTrue(txValidator.checkSolidity(tx.getHash()));
- assertTrue(txValidator.checkSolidity(tx.getHash()));
- }
-
- @Test
- public void verifyTxIsNotSolid() throws Exception {
- TransactionViewModel tx = getTxWithoutBranchAndTrunk();
- assertFalse(txValidator.checkSolidity(tx.getHash()));
- assertFalse(txValidator.checkSolidity(tx.getHash()));
- }
-
- @Test
- public void addSolidTransactionWithoutErrors() {
- byte[] trits = getTransactionTrits();
- Converter.copyTrits(0, trits, 0, trits.length);
- txValidator.addSolidTransaction(TransactionHash.calculate(SpongeFactory.Mode.CURLP81, trits));
- }
-
- private TransactionViewModel getTxWithBranchAndTrunk() throws Exception {
- TransactionViewModel tx, trunkTx, branchTx;
- String trytes = "999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999CFDEZBLZQYA9999999999999999999999999999999999999999999ZZWQHWD99C99999999C99999999CKWWDBWSCLMQULCTAAJGXDEMFJXPMGMAQIHDGHRBGEMUYNNCOK9YPHKEEFLFCZUSPMCJHAKLCIBQSGWAS999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999";
-
- byte[] trits = Converter.allocateTritsForTrytes(trytes.length());
- Converter.trits(trytes, trits, 0);
- trunkTx = new TransactionViewModel(trits, TransactionHash.calculate(SpongeFactory.Mode.CURLP81, trits));
- branchTx = new TransactionViewModel(trits, TransactionHash.calculate(SpongeFactory.Mode.CURLP81, trits));
-
- byte[] childTx = getTransactionTrits();
- System.arraycopy(trunkTx.getHash().trits(), 0, childTx, TransactionViewModel.TRUNK_TRANSACTION_TRINARY_OFFSET, TransactionViewModel.TRUNK_TRANSACTION_TRINARY_SIZE);
- System.arraycopy(branchTx.getHash().trits(), 0, childTx, TransactionViewModel.BRANCH_TRANSACTION_TRINARY_OFFSET, TransactionViewModel.BRANCH_TRANSACTION_TRINARY_SIZE);
- tx = new TransactionViewModel(childTx, TransactionHash.calculate(SpongeFactory.Mode.CURLP81, childTx));
-
- trunkTx.store(tangle, snapshotProvider.getInitialSnapshot());
- branchTx.store(tangle, snapshotProvider.getInitialSnapshot());
- tx.store(tangle, snapshotProvider.getInitialSnapshot());
-
- return tx;
- }
-
- @Test
- public void testTransactionPropagation() throws Exception {
- TransactionViewModel leftChildLeaf = TransactionTestUtils.createTransactionWithTrytes("CHILDTX");
- leftChildLeaf.updateSolid(true);
- leftChildLeaf.store(tangle, snapshotProvider.getInitialSnapshot());
-
- TransactionViewModel rightChildLeaf = TransactionTestUtils.createTransactionWithTrytes("CHILDTWOTX");
- rightChildLeaf.updateSolid(true);
- rightChildLeaf.store(tangle, snapshotProvider.getInitialSnapshot());
-
- TransactionViewModel parent = TransactionTestUtils.createTransactionWithTrunkAndBranch("PARENT",
- leftChildLeaf.getHash(), rightChildLeaf.getHash());
- parent.updateSolid(false);
- parent.store(tangle, snapshotProvider.getInitialSnapshot());
-
- TransactionViewModel parentSibling = TransactionTestUtils.createTransactionWithTrytes("PARENTLEAF");
- parentSibling.updateSolid(true);
- parentSibling.store(tangle, snapshotProvider.getInitialSnapshot());
-
- TransactionViewModel grandParent = TransactionTestUtils.createTransactionWithTrunkAndBranch("GRANDPARENT", parent.getHash(),
- parentSibling.getHash());
- grandParent.updateSolid(false);
- grandParent.store(tangle, snapshotProvider.getInitialSnapshot());
-
- txValidator.addSolidTransaction(leftChildLeaf.getHash());
- while (!txValidator.isNewSolidTxSetsEmpty()) {
- txValidator.propagateSolidTransactions();
- }
-
- parent = TransactionViewModel.fromHash(tangle, parent.getHash());
- assertTrue("Parent tx was expected to be solid", parent.isSolid());
- grandParent = TransactionViewModel.fromHash(tangle, grandParent.getHash());
- assertTrue("Grandparent was expected to be solid", grandParent.isSolid());
- }
-
- @Test
- public void testTransactionPropagationFailure() throws Exception {
- TransactionViewModel leftChildLeaf = new TransactionViewModel(getTransactionTrits(), getTransactionHash());
- leftChildLeaf.updateSolid(true);
- leftChildLeaf.store(tangle, snapshotProvider.getInitialSnapshot());
-
- TransactionViewModel rightChildLeaf = new TransactionViewModel(getTransactionTrits(), getTransactionHash());
- rightChildLeaf.updateSolid(true);
- rightChildLeaf.store(tangle, snapshotProvider.getInitialSnapshot());
-
- TransactionViewModel parent = new TransactionViewModel(getTransactionTritsWithTrunkAndBranch(leftChildLeaf.getHash(),
- rightChildLeaf.getHash()), getTransactionHash());
- parent.updateSolid(false);
- parent.store(tangle, snapshotProvider.getInitialSnapshot());
-
- TransactionViewModel parentSibling = new TransactionViewModel(getTransactionTrits(), getTransactionHash());
- parentSibling.updateSolid(false);
- parentSibling.store(tangle, snapshotProvider.getInitialSnapshot());
-
- TransactionViewModel grandParent = new TransactionViewModel(getTransactionTritsWithTrunkAndBranch(parent.getHash(),
- parentSibling.getHash()), getTransactionHash());
- grandParent.updateSolid(false);
- grandParent.store(tangle, snapshotProvider.getInitialSnapshot());
-
- txValidator.addSolidTransaction(leftChildLeaf.getHash());
- while (!txValidator.isNewSolidTxSetsEmpty()) {
- txValidator.propagateSolidTransactions();
- }
-
- parent = TransactionViewModel.fromHash(tangle, parent.getHash());
- assertTrue("Parent tx was expected to be solid", parent.isSolid());
- grandParent = TransactionViewModel.fromHash(tangle, grandParent.getHash());
- assertFalse("GrandParent tx was expected to be not solid", grandParent.isSolid());
- }
-
- private TransactionViewModel getTxWithoutBranchAndTrunk() throws Exception {
- byte[] trits = getTransactionTrits();
- TransactionViewModel tx = new TransactionViewModel(trits, TransactionHash.calculate(SpongeFactory.Mode.CURLP81, trits));
-
- tx.store(tangle, snapshotProvider.getInitialSnapshot());
-
- return tx;
- }
-}
diff --git a/src/test/java/com/iota/iri/network/NetworkInjectionConfigurationTest.java b/src/test/java/com/iota/iri/network/NetworkInjectionConfigurationTest.java
index c02749a893..5affbf062e 100644
--- a/src/test/java/com/iota/iri/network/NetworkInjectionConfigurationTest.java
+++ b/src/test/java/com/iota/iri/network/NetworkInjectionConfigurationTest.java
@@ -3,7 +3,8 @@
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
-import com.iota.iri.TransactionValidator;
+import com.iota.iri.service.validation.TransactionSolidifier;
+import com.iota.iri.service.validation.TransactionValidator;
import com.iota.iri.conf.BaseIotaConfig;
import com.iota.iri.conf.IotaConfig;
import com.iota.iri.network.pipeline.TransactionProcessingPipeline;
@@ -37,6 +38,11 @@ public void provideTransactionProcessingPipeline() {
assertNotNull("instance creation did not work", testInjector().getInstance(TransactionProcessingPipeline.class));
}
+ @Test
+ public void provideTransactionSolidifier(){
+ assertNotNull("instance creation did not work", testInjector().getInstance(TransactionSolidifier.class));
+ }
+
private Injector testInjector() {
IotaConfig config = mock(IotaConfig.class);
when(config.getCoordinator()).thenReturn(BaseIotaConfig.Defaults.COORDINATOR);
@@ -50,6 +56,7 @@ protected void configure() {
bind(LatestMilestoneTracker.class).toInstance(mock(LatestMilestoneTracker.class));
bind(SnapshotProvider.class).toInstance(mock(SnapshotProvider.class));
bind(TransactionValidator.class).toInstance(mock(TransactionValidator.class));
+ bind(TransactionSolidifier.class).toInstance(mock(TransactionSolidifier.class));
}
}
diff --git a/src/test/java/com/iota/iri/network/pipeline/ReceivedStageTest.java b/src/test/java/com/iota/iri/network/pipeline/ReceivedStageTest.java
index 772295c4e5..6ab434e8b6 100644
--- a/src/test/java/com/iota/iri/network/pipeline/ReceivedStageTest.java
+++ b/src/test/java/com/iota/iri/network/pipeline/ReceivedStageTest.java
@@ -1,6 +1,6 @@
package com.iota.iri.network.pipeline;
-import com.iota.iri.TransactionValidator;
+import com.iota.iri.service.validation.TransactionSolidifier;
import com.iota.iri.controllers.TransactionViewModel;
import com.iota.iri.network.TransactionRequester;
import com.iota.iri.network.neighbor.Neighbor;
@@ -26,7 +26,7 @@ public class ReceivedStageTest {
private Tangle tangle;
@Mock
- private TransactionValidator transactionValidator;
+ private TransactionSolidifier transactionSolidifier;
@Mock
private SnapshotProvider snapshotProvider;
@@ -49,7 +49,7 @@ public void newlyStoredTransactionUpdatesAlsoArrivalTimeAndSender() throws Excep
Mockito.when(neighbor.getMetrics()).thenReturn(neighborMetrics);
Mockito.when(transactionRequester.removeRecentlyRequestedTransaction(Mockito.any())).thenReturn(true);
- ReceivedStage stage = new ReceivedStage(tangle, transactionValidator, snapshotProvider, transactionRequester);
+ ReceivedStage stage = new ReceivedStage(tangle, transactionSolidifier, snapshotProvider, transactionRequester);
ReceivedPayload receivedPayload = new ReceivedPayload(neighbor, tvm);
ProcessingContext ctx = new ProcessingContext(null, receivedPayload);
stage.process(ctx);
@@ -58,11 +58,11 @@ public void newlyStoredTransactionUpdatesAlsoArrivalTimeAndSender() throws Excep
Mockito.verify(tvm).update(Mockito.any(), Mockito.any(), Mockito.any());
Mockito.verify(transactionRequester).removeRecentlyRequestedTransaction(Mockito.any());
Mockito.verify(transactionRequester).requestTrunkAndBranch(Mockito.any());
- assertEquals("should submit to broadcast stage next", TransactionProcessingPipeline.Stage.BROADCAST,
+ assertEquals("should submit to broadcast stage next", TransactionProcessingPipeline.Stage.SOLIDIFY,
ctx.getNextStage());
- BroadcastPayload broadcastPayload = (BroadcastPayload) ctx.getPayload();
- assertEquals("neighbor is still the same", neighbor, broadcastPayload.getOriginNeighbor());
- assertEquals("tvm is still the same", tvm, broadcastPayload.getTransactionViewModel());
+ SolidifyPayload solidifyPayload = (SolidifyPayload) ctx.getPayload();
+ assertEquals("neighbor is still the same", neighbor, solidifyPayload.getOriginNeighbor());
+ assertEquals("tvm is still the same", tvm, solidifyPayload.getTransaction());
}
@Test
@@ -70,7 +70,7 @@ public void alreadyStoredTransactionDoesNoUpdates() throws Exception {
Mockito.when(tvm.store(tangle, snapshotProvider.getInitialSnapshot())).thenReturn(false);
Mockito.when(neighbor.getMetrics()).thenReturn(neighborMetrics);
- ReceivedStage stage = new ReceivedStage(tangle, transactionValidator, snapshotProvider, transactionRequester);
+ ReceivedStage stage = new ReceivedStage(tangle, transactionSolidifier, snapshotProvider, transactionRequester);
ReceivedPayload receivedPayload = new ReceivedPayload(neighbor, tvm);
ProcessingContext ctx = new ProcessingContext(null, receivedPayload);
stage.process(ctx);
@@ -79,11 +79,11 @@ public void alreadyStoredTransactionDoesNoUpdates() throws Exception {
Mockito.verify(tvm, Mockito.never()).update(Mockito.any(), Mockito.any(), Mockito.any());
Mockito.verify(transactionRequester).removeRecentlyRequestedTransaction(Mockito.any());
Mockito.verify(transactionRequester, Mockito.never()).requestTrunkAndBranch(Mockito.any());
- assertEquals("should submit to broadcast stage next", TransactionProcessingPipeline.Stage.BROADCAST,
+ assertEquals("should submit to broadcast stage next", TransactionProcessingPipeline.Stage.SOLIDIFY,
ctx.getNextStage());
- BroadcastPayload broadcastPayload = (BroadcastPayload) ctx.getPayload();
- assertEquals("neighbor should still be the same", neighbor, broadcastPayload.getOriginNeighbor());
- assertEquals("tvm should still be the same", tvm, broadcastPayload.getTransactionViewModel());
+ SolidifyPayload solidifyPayload = (SolidifyPayload) ctx.getPayload();
+ assertEquals("neighbor should still be the same", neighbor, solidifyPayload.getOriginNeighbor());
+ assertEquals("tvm should still be the same", tvm, solidifyPayload.getTransaction());
}
}
\ No newline at end of file
diff --git a/src/test/java/com/iota/iri/network/pipeline/SolidifyStageTest.java b/src/test/java/com/iota/iri/network/pipeline/SolidifyStageTest.java
new file mode 100644
index 0000000000..958c5f3259
--- /dev/null
+++ b/src/test/java/com/iota/iri/network/pipeline/SolidifyStageTest.java
@@ -0,0 +1,115 @@
+package com.iota.iri.network.pipeline;
+
+import com.iota.iri.controllers.TipsViewModel;
+import com.iota.iri.controllers.TransactionViewModel;
+import com.iota.iri.model.Hash;
+import com.iota.iri.model.persistables.Transaction;
+import com.iota.iri.service.validation.TransactionSolidifier;
+import com.iota.iri.storage.Tangle;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import static org.junit.Assert.assertEquals;
+
+public class SolidifyStageTest {
+ @Rule
+ public MockitoRule mockitoRule = MockitoJUnit.rule();
+
+ @Mock
+ private Tangle tangle;
+
+ @Mock
+ private TipsViewModel tipsViewModel;
+
+ @Mock
+ private TransactionViewModel tvm;
+
+ @Mock
+ private Hash originalHash;
+
+ @Mock
+ private Hash tipHash;
+
+ @Mock
+ private TransactionSolidifier transactionSolidifier;
+
+ @Test
+ public void solidTransactionIsBroadcast() throws Exception{
+ Mockito.when(tvm.isSolid()).thenReturn(true);
+ Mockito.when(tvm.getHash()).thenReturn(originalHash);
+
+ SolidifyStage solidifyStage = new SolidifyStage(transactionSolidifier, tipsViewModel, tangle);
+ SolidifyPayload solidifyPayload = new SolidifyPayload(null, tvm);
+ ProcessingContext ctx = new ProcessingContext(solidifyPayload);
+
+ solidifyStage.process(ctx);
+ Thread.sleep(100);
+
+ assertEquals("Expected next stage to be broadcast", ctx.getNextStage(),
+ TransactionProcessingPipeline.Stage.BROADCAST);
+ BroadcastPayload broadcastPayload = (BroadcastPayload) ctx.getPayload();
+ assertEquals("Expected payload hash to equal the original transaction hash",
+ broadcastPayload.getTransactionViewModel().getHash(), originalHash);
+ }
+
+ @Test
+ public void quickSetSolidTransactionIsBroadcast() throws Exception{
+ Mockito.when(transactionSolidifier.quickSetSolid(tvm)).thenReturn(true);
+ Mockito.when(tvm.getHash()).thenReturn(originalHash);
+
+ SolidifyStage solidifyStage = new SolidifyStage(transactionSolidifier, tipsViewModel, tangle);
+ SolidifyPayload solidifyPayload = new SolidifyPayload(null, tvm);
+ ProcessingContext ctx = new ProcessingContext(solidifyPayload);
+
+ solidifyStage.process(ctx);
+ Thread.sleep(100);
+
+ assertEquals("Expected next stage to be broadcast", ctx.getNextStage(),
+ TransactionProcessingPipeline.Stage.BROADCAST);
+ BroadcastPayload broadcastPayload = (BroadcastPayload) ctx.getPayload();
+ assertEquals("Expected payload hash to equal the original transaction hash",
+ broadcastPayload.getTransactionViewModel().getHash(), originalHash);
+ }
+
+ @Test
+ public void unsolidTransactionBroadcastsRandomSolidTip() throws Exception{
+ Mockito.when(tvm.isSolid()).thenReturn(false);
+ Mockito.when(transactionSolidifier.quickSetSolid(tvm)).thenReturn(false);
+ TransactionViewModel tip = new TransactionViewModel(new Transaction(), tipHash);
+
+ SolidifyStage solidifyStage = new SolidifyStage(transactionSolidifier, tipsViewModel, tangle);
+ SolidifyPayload solidifyPayload = new SolidifyPayload(null, tvm);
+ ProcessingContext ctx = new ProcessingContext(solidifyPayload);
+
+ solidifyStage.injectTip(tip);
+ solidifyStage.process(ctx);
+ Thread.sleep(100);
+
+ assertEquals("Expected next stage to be broadcast", ctx.getNextStage(),
+ TransactionProcessingPipeline.Stage.BROADCAST);
+ BroadcastPayload broadcastPayload = (BroadcastPayload) ctx.getPayload();
+ assertEquals("Expected payload hash to equal random tip hash",
+ broadcastPayload.getTransactionViewModel().getHash(), tipHash);
+ }
+
+ @Test
+ public void unsolidWithNoRandomTipsAborts() throws Exception{
+ Mockito.when(tvm.isSolid()).thenReturn(false);
+ Mockito.when(transactionSolidifier.quickSetSolid(tvm)).thenReturn(false);
+ Mockito.when(tipsViewModel.getRandomSolidTipHash()).thenReturn(null);
+
+ SolidifyStage solidifyStage = new SolidifyStage(transactionSolidifier, tipsViewModel, tangle);
+ SolidifyPayload solidifyPayload = new SolidifyPayload(null, tvm);
+ ProcessingContext ctx = new ProcessingContext(solidifyPayload);
+
+ solidifyStage.process(ctx);
+ Thread.sleep(100);
+
+ assertEquals("Expected next stage to be broadcast", ctx.getNextStage(),
+ TransactionProcessingPipeline.Stage.FINISH);
+ }
+}
diff --git a/src/test/java/com/iota/iri/network/pipeline/TransactionProcessingPipelineTest.java b/src/test/java/com/iota/iri/network/pipeline/TransactionProcessingPipelineTest.java
index b9b8c4ab8f..576f418bc0 100644
--- a/src/test/java/com/iota/iri/network/pipeline/TransactionProcessingPipelineTest.java
+++ b/src/test/java/com/iota/iri/network/pipeline/TransactionProcessingPipelineTest.java
@@ -1,6 +1,7 @@
package com.iota.iri.network.pipeline;
-import com.iota.iri.TransactionValidator;
+import com.iota.iri.service.validation.TransactionSolidifier;
+import com.iota.iri.service.validation.TransactionValidator;
import com.iota.iri.conf.NodeConfig;
import com.iota.iri.controllers.TipsViewModel;
import com.iota.iri.network.NeighborRouter;
@@ -18,6 +19,7 @@
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
+
public class TransactionProcessingPipelineTest {
@Rule
@@ -74,6 +76,15 @@ public class TransactionProcessingPipelineTest {
@Mock
private HashingPayload hashingPayload;
+ @Mock
+ private BroadcastPayload broadcastPayload;
+
+ @Mock
+ private SolidifyStage solidifyStage;
+
+ @Mock
+ private SolidifyPayload solidifyPayload;
+
@Mock
private ProcessingContext validationCtx;
@@ -89,9 +100,15 @@ public class TransactionProcessingPipelineTest {
@Mock
private ProcessingContext broadcastCtx;
+ @Mock
+ private ProcessingContext solidifyCtx;
+
@Mock
private ProcessingContext abortCtx;
+ @Mock
+ private TransactionSolidifier transactionSolidifier;
+
private void mockHashingStage(TransactionProcessingPipeline pipeline) {
Mockito.when(hashingPayload.getTxTrits()).thenReturn(null);
Mockito.doAnswer(invocation -> {
@@ -107,6 +124,7 @@ private void injectMockedStagesIntoPipeline(TransactionProcessingPipeline pipeli
pipeline.setHashingStage(hashingStage);
pipeline.setReplyStage(replyStage);
pipeline.setValidationStage(validationStage);
+ pipeline.setSolidifyStage(solidifyStage);
}
@Test
@@ -114,7 +132,7 @@ public void processingAValidNewTransactionFlowsThroughTheEntirePipeline() throws
TransactionProcessingPipeline pipeline = new TransactionProcessingPipelineImpl(neighborRouter, nodeConfig,
transactionValidator, tangle, snapshotProvider, tipsViewModel, latestMilestoneTracker,
- transactionRequester);
+ transactionRequester, transactionSolidifier);
// inject mocks
injectMockedStagesIntoPipeline(pipeline);
@@ -136,7 +154,13 @@ public void processingAValidNewTransactionFlowsThroughTheEntirePipeline() throws
// mock received
Mockito.when(broadcastCtx.getNextStage()).thenReturn(TransactionProcessingPipeline.Stage.BROADCAST);
- Mockito.when(receivedStage.process(receivedCtx)).thenReturn(broadcastCtx);
+ Mockito.when(broadcastCtx.getPayload()).thenReturn(broadcastPayload);
+ Mockito.when(receivedStage.process(receivedCtx)).thenReturn(solidifyCtx);
+
+ // mock solidify
+ Mockito.when(solidifyCtx.getPayload()).thenReturn(solidifyPayload);
+ Mockito.when(solidifyCtx.getNextStage()).thenReturn(TransactionProcessingPipeline.Stage.SOLIDIFY);
+ Mockito.when(solidifyStage.process(solidifyCtx)).thenReturn(broadcastCtx);
pipeline.start();
@@ -152,6 +176,7 @@ public void processingAValidNewTransactionFlowsThroughTheEntirePipeline() throws
Mockito.verify(validationStage).process(Mockito.any());
Mockito.verify(receivedStage).process(Mockito.any());
Mockito.verify(replyStage).process(Mockito.any());
+ Mockito.verify(solidifyStage).process(Mockito.any());
Mockito.verify(broadcastStage).process(Mockito.any());
}
@@ -159,7 +184,7 @@ public void processingAValidNewTransactionFlowsThroughTheEntirePipeline() throws
public void processingAKnownTransactionOnlyFlowsToTheReplyStage() throws InterruptedException {
TransactionProcessingPipeline pipeline = new TransactionProcessingPipelineImpl(neighborRouter, nodeConfig,
transactionValidator, tangle, snapshotProvider, tipsViewModel, latestMilestoneTracker,
- transactionRequester);
+ transactionRequester, transactionSolidifier);
// inject mocks
pipeline.setPreProcessStage(preProcessStage);
@@ -181,6 +206,7 @@ public void processingAKnownTransactionOnlyFlowsToTheReplyStage() throws Interru
Mockito.verify(hashingStage, Mockito.never()).process(Mockito.any());
Mockito.verify(validationStage, Mockito.never()).process(Mockito.any());
Mockito.verify(receivedStage, Mockito.never()).process(Mockito.any());
+ Mockito.verify(solidifyStage, Mockito.never()).process(Mockito.any());
Mockito.verify(broadcastStage, Mockito.never()).process(Mockito.any());
// should have called
@@ -193,7 +219,7 @@ public void processingAValidNewTransactionNotOriginatingFromANeighborFlowsThroug
throws InterruptedException {
TransactionProcessingPipeline pipeline = new TransactionProcessingPipelineImpl(neighborRouter, nodeConfig,
transactionValidator, tangle, snapshotProvider, tipsViewModel, latestMilestoneTracker,
- transactionRequester);
+ transactionRequester, transactionSolidifier);
// inject mocks
injectMockedStagesIntoPipeline(pipeline);
@@ -202,7 +228,6 @@ public void processingAValidNewTransactionNotOriginatingFromANeighborFlowsThroug
Mockito.when(hashingCtx.getNextStage()).thenReturn(TransactionProcessingPipeline.Stage.HASHING);
Mockito.when(hashingCtx.getPayload()).thenReturn(hashingPayload);
- // mock hashing context/stage
// mock hashing context/stage
mockHashingStage(pipeline);
@@ -212,7 +237,12 @@ public void processingAValidNewTransactionNotOriginatingFromANeighborFlowsThroug
// mock received
Mockito.when(broadcastCtx.getNextStage()).thenReturn(TransactionProcessingPipeline.Stage.BROADCAST);
- Mockito.when(receivedStage.process(receivedCtx)).thenReturn(broadcastCtx);
+ Mockito.when(receivedStage.process(receivedCtx)).thenReturn(solidifyCtx);
+
+ // mock solidify
+ Mockito.when(solidifyCtx.getPayload()).thenReturn(solidifyPayload);
+ Mockito.when(solidifyCtx.getNextStage()).thenReturn(TransactionProcessingPipeline.Stage.SOLIDIFY);
+ Mockito.when(solidifyStage.process(solidifyCtx)).thenReturn(broadcastCtx);
pipeline.start();
@@ -231,6 +261,7 @@ public void processingAValidNewTransactionNotOriginatingFromANeighborFlowsThroug
Mockito.verify(hashingStage).process(Mockito.any());
Mockito.verify(validationStage).process(Mockito.any());
Mockito.verify(receivedStage).process(Mockito.any());
+ Mockito.verify(solidifyStage).process(Mockito.any());
Mockito.verify(broadcastStage).process(Mockito.any());
}
@@ -238,7 +269,7 @@ public void processingAValidNewTransactionNotOriginatingFromANeighborFlowsThroug
public void anInvalidNewTransactionStopsBeingProcessedAfterTheValidationStage() throws InterruptedException {
TransactionProcessingPipeline pipeline = new TransactionProcessingPipelineImpl(neighborRouter, nodeConfig,
transactionValidator, tangle, snapshotProvider, tipsViewModel, latestMilestoneTracker,
- transactionRequester);
+ transactionRequester, transactionSolidifier);
// inject mocks
injectMockedStagesIntoPipeline(pipeline);
@@ -269,6 +300,7 @@ public void anInvalidNewTransactionStopsBeingProcessedAfterTheValidationStage()
Mockito.verify(preProcessStage, Mockito.never()).process(Mockito.any());
Mockito.verify(broadcastStage, Mockito.never()).process(Mockito.any());
Mockito.verify(receivedStage, Mockito.never()).process(Mockito.any());
+ Mockito.verify(solidifyStage, Mockito.never()).process(Mockito.any());
// should have called
Mockito.verify(hashingStage).process(Mockito.any());
diff --git a/src/test/java/com/iota/iri/network/pipeline/ValidationStageTest.java b/src/test/java/com/iota/iri/network/pipeline/ValidationStageTest.java
index 5bd0eecdff..c02d57d3b4 100644
--- a/src/test/java/com/iota/iri/network/pipeline/ValidationStageTest.java
+++ b/src/test/java/com/iota/iri/network/pipeline/ValidationStageTest.java
@@ -1,6 +1,6 @@
package com.iota.iri.network.pipeline;
-import com.iota.iri.TransactionValidator;
+import com.iota.iri.service.validation.TransactionValidator;
import com.iota.iri.controllers.TransactionViewModel;
import com.iota.iri.model.Hash;
import com.iota.iri.network.FIFOCache;
diff --git a/src/test/java/com/iota/iri/service/APITest.java b/src/test/java/com/iota/iri/service/APITest.java
index f5639156fb..7169f339ad 100644
--- a/src/test/java/com/iota/iri/service/APITest.java
+++ b/src/test/java/com/iota/iri/service/APITest.java
@@ -1,6 +1,7 @@
package com.iota.iri.service;
-import com.iota.iri.TransactionValidator;
+import com.iota.iri.service.validation.TransactionSolidifier;
+import com.iota.iri.service.validation.TransactionValidator;
import com.iota.iri.conf.IotaConfig;
import com.iota.iri.controllers.TransactionViewModel;
import com.iota.iri.service.snapshot.SnapshotProvider;
@@ -28,6 +29,9 @@ public class APITest {
@Mock(answer = Answers.RETURNS_SMART_NULLS)
private TransactionValidator transactionValidator;
+ @Mock
+ private TransactionSolidifier transactionSolidifier;
+
@Mock
private SnapshotProvider snapshotProvider;
@@ -43,7 +47,7 @@ public void whenStoreTransactionsStatementThenSetArrivalTimeToCurrentMillis() th
API api = new API(config, null, null, null,
null, null,
snapshotProvider, null, null, null, null,
- transactionValidator, null, null);
+ transactionValidator, null, null, transactionSolidifier);
api.storeTransactionsStatement(Collections.singletonList("FOO"));
diff --git a/src/test/java/com/iota/iri/service/ApiCallTest.java b/src/test/java/com/iota/iri/service/ApiCallTest.java
index a6f4f0de39..d2ca7dc9b0 100644
--- a/src/test/java/com/iota/iri/service/ApiCallTest.java
+++ b/src/test/java/com/iota/iri/service/ApiCallTest.java
@@ -14,7 +14,7 @@ public class ApiCallTest {
@Before
public void setUp() {
IotaConfig configuration = Mockito.mock(IotaConfig.class);
- api = new API(configuration, null, null, null, null, null, null, null, null, null, null, null, null, null);
+ api = new API(configuration, null, null, null, null, null, null, null, null, null, null, null, null, null, null);
}
@Test
diff --git a/src/test/java/com/iota/iri/service/validation/TransactionValidatorTest.java b/src/test/java/com/iota/iri/service/validation/TransactionValidatorTest.java
new file mode 100644
index 0000000000..596d792758
--- /dev/null
+++ b/src/test/java/com/iota/iri/service/validation/TransactionValidatorTest.java
@@ -0,0 +1,100 @@
+package com.iota.iri.service.validation;
+
+import com.iota.iri.conf.MainnetConfig;
+import com.iota.iri.conf.ProtocolConfig;
+import com.iota.iri.crypto.SpongeFactory;
+import com.iota.iri.network.TransactionRequester;
+import com.iota.iri.service.snapshot.SnapshotProvider;
+import com.iota.iri.service.snapshot.impl.SnapshotMockUtils;
+import com.iota.iri.storage.Tangle;
+import com.iota.iri.storage.rocksDB.RocksDBPersistenceProvider;
+import com.iota.iri.utils.Converter;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.Rule;
+import org.junit.AfterClass;
+import org.junit.rules.TemporaryFolder;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import static com.iota.iri.TransactionTestUtils.getTransactionTrits;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class TransactionValidatorTest {
+
+ private static final int MAINNET_MWM = 14;
+ private static final TemporaryFolder dbFolder = new TemporaryFolder();
+ private static final TemporaryFolder logFolder = new TemporaryFolder();
+ private static Tangle tangle;
+ private static TransactionValidator txValidator;
+
+ @Rule
+ public MockitoRule mockitoRule = MockitoJUnit.rule();
+
+ @Mock
+ private static SnapshotProvider snapshotProvider;
+
+ @Mock
+ private static TransactionRequester txRequester;
+
+ @BeforeClass
+ public static void setUp() throws Exception {
+ dbFolder.create();
+ logFolder.create();
+ tangle = new Tangle();
+ tangle.addPersistenceProvider(
+ new RocksDBPersistenceProvider(
+ dbFolder.getRoot().getAbsolutePath(), logFolder.getRoot().getAbsolutePath(),1000, Tangle.COLUMN_FAMILIES, Tangle.METADATA_COLUMN_FAMILY));
+ tangle.init();
+ }
+
+ @AfterClass
+ public static void tearDown() throws Exception {
+ tangle.shutdown();
+ dbFolder.delete();
+ logFolder.delete();
+ }
+
+ @Before
+ public void setUpEach() {
+ when(snapshotProvider.getInitialSnapshot()).thenReturn(SnapshotMockUtils.createSnapshot());
+ txRequester = new TransactionRequester(tangle, snapshotProvider);
+ txValidator = new TransactionValidator(snapshotProvider, txRequester, new MainnetConfig());
+ txValidator.setMwm(false, MAINNET_MWM);
+ }
+
+ @Test
+ public void testMinMwm() {
+ ProtocolConfig protocolConfig = mock(ProtocolConfig.class);
+ when(protocolConfig.getMwm()).thenReturn(5);
+ TransactionValidator transactionValidator = new TransactionValidator(null, null, protocolConfig);
+ assertEquals("Expected testnet minimum minWeightMagnitude", 13, transactionValidator.getMinWeightMagnitude());
+ }
+
+ @Test
+ public void validateTrits() {
+ byte[] trits = getTransactionTrits();
+ Converter.copyTrits(0, trits, 0, trits.length);
+ txValidator.validateTrits(trits, MAINNET_MWM);
+ }
+
+ @Test(expected = RuntimeException.class)
+ public void validateTritsWithInvalidMetadata() {
+ byte[] trits = getTransactionTrits();
+ txValidator.validateTrits(trits, MAINNET_MWM);
+ }
+
+ @Test
+ public void validateBytesWithNewCurl() {
+ byte[] trits = getTransactionTrits();
+ Converter.copyTrits(0, trits, 0, trits.length);
+ byte[] bytes = Converter.allocateBytesForTrits(trits.length);
+ Converter.bytes(trits, 0, bytes, 0, trits.length);
+ txValidator.validateBytes(bytes, txValidator.getMinWeightMagnitude(), SpongeFactory.create(SpongeFactory.Mode.CURLP81));
+ }
+}
diff --git a/src/test/java/com/iota/iri/service/validation/impl/TransactionSolidifierImplTest.java b/src/test/java/com/iota/iri/service/validation/impl/TransactionSolidifierImplTest.java
new file mode 100644
index 0000000000..744357c3da
--- /dev/null
+++ b/src/test/java/com/iota/iri/service/validation/impl/TransactionSolidifierImplTest.java
@@ -0,0 +1,160 @@
+package com.iota.iri.service.validation.impl;
+
+import com.iota.iri.controllers.TipsViewModel;
+import com.iota.iri.controllers.TransactionViewModel;
+import com.iota.iri.crypto.SpongeFactory;
+import com.iota.iri.model.TransactionHash;
+import com.iota.iri.network.TransactionRequester;
+import com.iota.iri.service.snapshot.SnapshotProvider;
+import com.iota.iri.service.snapshot.impl.SnapshotMockUtils;
+import com.iota.iri.storage.Tangle;
+import com.iota.iri.storage.rocksDB.RocksDBPersistenceProvider;
+import com.iota.iri.utils.Converter;
+import org.junit.Rule;
+import org.junit.BeforeClass;
+import org.junit.Before;
+import org.junit.AfterClass;
+import org.junit.After;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import static com.iota.iri.TransactionTestUtils.getTransactionTrits;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.when;
+
+public class TransactionSolidifierImplTest {
+ private static final TemporaryFolder dbFolder = new TemporaryFolder();
+ private static final TemporaryFolder logFolder = new TemporaryFolder();
+ private static Tangle tangle;
+
+ @Rule
+ public MockitoRule mockitoRule = MockitoJUnit.rule();
+
+ @Mock
+ private static SnapshotProvider snapshotProvider;
+
+ @Mock
+ private static TransactionSolidifierImpl txSolidifier;
+
+ @Mock
+ private static TipsViewModel tipsViewModel;
+
+ @Mock
+ private static TransactionRequester txRequester;
+
+ @BeforeClass
+ public static void setUp() throws Exception {
+ dbFolder.create();
+ logFolder.create();
+ tangle = new Tangle();
+ tangle.addPersistenceProvider(
+ new RocksDBPersistenceProvider(
+ dbFolder.getRoot().getAbsolutePath(), logFolder.getRoot().getAbsolutePath(),1000, Tangle.COLUMN_FAMILIES, Tangle.METADATA_COLUMN_FAMILY));
+ tangle.init();
+ }
+
+ @AfterClass
+ public static void tearDown() throws Exception {
+ tangle.shutdown();
+ dbFolder.delete();
+ logFolder.delete();
+ }
+
+ @Before
+ public void setUpEach() {
+ when(snapshotProvider.getInitialSnapshot()).thenReturn(SnapshotMockUtils.createSnapshot());
+ txRequester = new TransactionRequester(tangle, snapshotProvider);
+ txSolidifier = new TransactionSolidifierImpl(tangle, snapshotProvider, txRequester, tipsViewModel);
+ txSolidifier.start();
+ }
+
+ @After
+ public void tearDownEach(){
+ txSolidifier.shutdown();
+ }
+
+
+ @Test
+ public void verifyTxIsSolid() throws Exception {
+ TransactionViewModel tx = getTxWithBranchAndTrunk();
+ assertTrue("Expected transaction to be solid", txSolidifier.checkSolidity(tx.getHash()));
+ assertTrue("Expected transaction to be solid", txSolidifier.checkSolidity(tx.getHash()));
+ }
+
+ @Test
+ public void verifyTxIsNotSolid() throws Exception {
+ TransactionViewModel tx = getTxWithoutBranchAndTrunk();
+ assertFalse("Expected transaction to fail solidity check", txSolidifier.checkSolidity(tx.getHash()));
+ assertFalse("Expected transaction to fail solidity check", txSolidifier.checkSolidity(tx.getHash()));
+ }
+
+ @Test
+ public void getSolidificationQueue() throws Exception {
+ TransactionViewModel mainTx = getTxWithBranchAndTrunk();
+ for(int i = 0; i < 10; i++) {
+ TransactionViewModel tx = getTxWithBranchAndTrunk();
+ txSolidifier.addToSolidificationQueue(tx.getHash());
+ }
+ txSolidifier.addToSolidificationQueue(mainTx.getHash());
+ assertTrue("Expected transaction to be present in the solidification queue",
+ txSolidifier.getSolidificationQueue().contains(mainTx.getHash()));
+ }
+
+ @Test
+ public void verifyTransactionIsProcessedFully() throws Exception {
+ TransactionViewModel tx = getTxWithBranchAndTrunk();
+ txSolidifier.addToSolidificationQueue(tx.getHash());
+
+ //Time to process through the steps
+ Thread.sleep(1000);
+ assertTrue("Expected transaction to be present in the broadcast queue",
+ txSolidifier.getBroadcastQueue().contains(tx));
+ }
+
+
+ @Test
+ public void verifyInconsistentTransactionIsNotProcessedFully() throws Exception {
+ TransactionViewModel tx = getTxWithoutBranchAndTrunk();
+ txSolidifier.addToSolidificationQueue(tx.getHash());
+
+ //Time to process through the steps
+ Thread.sleep(1000);
+ assertFalse("Expected transaction not to be present in the broadcast queue",
+ txSolidifier.getBroadcastQueue().contains(tx));
+ }
+
+ private TransactionViewModel getTxWithBranchAndTrunk() throws Exception {
+ TransactionViewModel tx, trunkTx, branchTx;
+ String trytes = "999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999CFDEZBLZQYA9999999999999999999999999999999999999999999ZZWQHWD99C99999999C99999999CKWWDBWSCLMQULCTAAJGXDEMFJXPMGMAQIHDGHRBGEMUYNNCOK9YPHKEEFLFCZUSPMCJHAKLCIBQSGWAS999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999";
+
+ byte[] trits = Converter.allocateTritsForTrytes(trytes.length());
+ Converter.trits(trytes, trits, 0);
+ trunkTx = new TransactionViewModel(trits, TransactionHash.calculate(SpongeFactory.Mode.CURLP81, trits));
+ branchTx = new TransactionViewModel(trits, TransactionHash.calculate(SpongeFactory.Mode.CURLP81, trits));
+
+ byte[] childTx = getTransactionTrits();
+ System.arraycopy(trunkTx.getHash().trits(), 0, childTx, TransactionViewModel.TRUNK_TRANSACTION_TRINARY_OFFSET, TransactionViewModel.TRUNK_TRANSACTION_TRINARY_SIZE);
+ System.arraycopy(branchTx.getHash().trits(), 0, childTx, TransactionViewModel.BRANCH_TRANSACTION_TRINARY_OFFSET, TransactionViewModel.BRANCH_TRANSACTION_TRINARY_SIZE);
+ tx = new TransactionViewModel(childTx, TransactionHash.calculate(SpongeFactory.Mode.CURLP81, childTx));
+
+ trunkTx.store(tangle, snapshotProvider.getInitialSnapshot());
+ branchTx.store(tangle, snapshotProvider.getInitialSnapshot());
+ tx.store(tangle, snapshotProvider.getInitialSnapshot());
+
+ return tx;
+ }
+
+ private TransactionViewModel getTxWithoutBranchAndTrunk() throws Exception {
+ byte[] trits = getTransactionTrits();
+ TransactionViewModel tx = new TransactionViewModel(trits, TransactionHash.calculate(SpongeFactory.Mode.CURLP81, trits));
+
+ tx.store(tangle, snapshotProvider.getInitialSnapshot());
+
+ return tx;
+ }
+
+}