diff --git a/changelog.txt b/changelog.txt index 02023040ed..55532edbf1 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,10 @@ +1.8.5 + +Protocol-Change: + Added bundle validity rules - check that bundles confirm only tails and 2 bundles at most (#1786) + Check validity rules on tip-sel and check-consistency only (#1802) + + 1.8.4 Hotfix: Ensure proper creation of solid entrypoints (#1702) diff --git a/pom.xml b/pom.xml index 33578a5696..859f2a8e5e 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.iota iri - 1.8.4 + 1.8.5 IRI IOTA Reference Implementation 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..b663009f1a 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 -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger(__name__) +from util import logger as log +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..a1e758e66f 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,7 @@ def wait_for_update(index, api): if node_info['latestSolidSubtangleMilestoneIndex'] == index: updated = True break - i += 1; + i += 1 sleep(1) 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/BundleValidator.java b/src/main/java/com/iota/iri/BundleValidator.java index 4242ada537..6327c44d3a 100644 --- a/src/main/java/com/iota/iri/BundleValidator.java +++ b/src/main/java/com/iota/iri/BundleValidator.java @@ -8,6 +8,7 @@ import com.iota.iri.utils.Converter; import java.util.*; +import com.google.common.annotations.VisibleForTesting; /** * Validates bundles. @@ -55,22 +56,33 @@ public enum Validity { */ public static final int MODE_VALIDATE_SEMANTICS = 1 << 2; + /** + * Instructs the validation code to validate all transactions within the bundle approve via their branch the trunk + * transaction of the head transaction + */ + public static final int MODE_VALIDATE_BUNDLE_TX_APPROVAL = 1 << 3; + + /** + * Instructs the validation code to validate that the bundle only approves tail txs. + */ + public static final int MODE_VALIDATE_TAIL_APPROVAL = 1 << 4; + /** * Instructs the validation code to fully validate the semantics, bundle hash and signatures of the given bundle. */ - public static final int MODE_VALIDATE_ALL = MODE_VALIDATE_SIGNATURES | MODE_VALIDATE_BUNDLE_HASH | MODE_VALIDATE_SEMANTICS; + public static final int MODE_VALIDATE_ALL = MODE_VALIDATE_SIGNATURES | MODE_VALIDATE_BUNDLE_HASH | MODE_VALIDATE_SEMANTICS | MODE_VALIDATE_TAIL_APPROVAL | MODE_VALIDATE_BUNDLE_TX_APPROVAL; /** * Instructs the validation code to skip checking the bundle's already computed validity and instead to proceed to * validate the bundle further. */ - public static final int MODE_SKIP_CACHED_VALIDITY = 1 << 3; + public static final int MODE_SKIP_CACHED_VALIDITY = 1 << 5; /** * Instructs the validation code to skip checking whether the tail transaction is present or a tail transaction was * given as the start transaction. */ - public static final int MODE_SKIP_TAIL_TX_EXISTENCE = 1 << 4; + public static final int MODE_SKIP_TAIL_TX_EXISTENCE = 1 << 6; /** * Fetches a bundle of transactions identified by the {@code tailHash} and validates the transactions. Bundle is a @@ -85,6 +97,8 @@ public enum Validity { *
  • Total bundle value is 0 (inputs and outputs are balanced)
  • *
  • Recalculate the bundle hash by absorbing and squeezing the transactions' essence
  • *
  • Validate the signature on input transactions
  • + *
  • The bundle must only approve tail transactions
  • + *
  • All transactions within the bundle approve via their branch the trunk transaction of the head transaction.
  • * *

    * As well as the following syntactic checks: @@ -96,9 +110,11 @@ public enum Validity { * we lose the last trit in the process * * - * @param tangle used to fetch the bundle's transactions from the persistence layer - * @param initialSnapshot the initial snapshot that defines the genesis for our ledger state - * @param tailHash the hash of the last transaction in a bundle. + * @param tangle used to fetch the bundle's transactions from the persistence layer + * @param enforceExtraRules true if enforce {@link #validateBundleTailApproval(Tangle, List)} and + * {@link #validateBundleTransactionsApproval(List)} + * @param initialSnapshot the initial snapshot that defines the genesis for our ledger state + * @param tailHash the hash of the last transaction in a bundle. * @return A list of transactions of the bundle contained in another list. If the bundle is valid then the tail * transaction's {@link TransactionViewModel#getValidity()} will return 1, else {@link * TransactionViewModel#getValidity()} will return -1. If the bundle is invalid then an empty list will be @@ -108,9 +124,32 @@ public enum Validity { * validate it again. *

    */ - public List validate(Tangle tangle, Snapshot initialSnapshot, Hash tailHash) throws Exception { + public List validate(Tangle tangle, boolean enforceExtraRules, Snapshot initialSnapshot, + Hash tailHash) throws Exception { + int mode = getMode(enforceExtraRules); + return validate(tangle, initialSnapshot, tailHash, mode); + } + + /** + * Does {@link #validate(Tangle, boolean, Snapshot, Hash)} but with an option of skipping some checks according to + * the give {@code mode} + * + * @param tangle used to fetch the bundle's transactions from the persistence layer + * @param initialSnapshot the initial snapshot that defines the genesis for our ledger state + * @param tailHash the hash of the last transaction in a bundle. + * @param mode flags that specify which validation checks to perform + * @return A list of transactions of the bundle contained in another list. If the bundle is valid then the tail + * transaction's {@link TransactionViewModel#getValidity()} will return 1, else + * {@link TransactionViewModel#getValidity()} will return -1. If the bundle is invalid then an empty list + * will be returned. + * @throws Exception if a persistence error occurred + * @implNote if {@code tailHash} was already invalidated/validated by a previous call to this method then we don't + * validate it again. + * @see #validate(Tangle, boolean, Snapshot, Hash) + */ + private List validate(Tangle tangle, Snapshot initialSnapshot, Hash tailHash, int mode) throws Exception { List bundleTxs = new LinkedList<>(); - switch (validate(tangle, tailHash, MODE_VALIDATE_ALL, bundleTxs)) { + switch (validate(tangle, tailHash, mode, bundleTxs)) { case VALID: if (bundleTxs.get(0).getValidity() != 1) { bundleTxs.get(0).setValidity(tangle, initialSnapshot, 1); @@ -126,7 +165,14 @@ public List validate(Tangle tangle, Snapshot initialSnapsh } } - private static boolean hasMode(int mode, int has) { + private static int getMode(boolean enforceExtraRules) { + if (enforceExtraRules) { + return MODE_VALIDATE_ALL; + } + return MODE_VALIDATE_SIGNATURES | MODE_VALIDATE_BUNDLE_HASH | MODE_VALIDATE_SEMANTICS; + } + + private static boolean hasMode(int mode, int has) { return (mode & has) == has; } @@ -147,7 +193,9 @@ private static boolean hasMode(int mode, int has) { * @return whether the validation criteria were passed or not * @throws Exception if an error occurred in the persistence layer */ - public static Validity validate(Tangle tangle, Hash startTxHash, int validationMode, List bundleTxs) throws Exception { + @VisibleForTesting + Validity validate(Tangle tangle, Hash startTxHash, int validationMode, List bundleTxs) + throws Exception { TransactionViewModel startTx = TransactionViewModel.fromHash(tangle, startTxHash); if (startTx == null || (!hasMode(validationMode, MODE_SKIP_TAIL_TX_EXISTENCE) && (startTx.getCurrentIndex() != 0 || startTx.getValidity() == -1))) { @@ -160,7 +208,8 @@ public static Validity validate(Tangle tangle, Hash startTxHash, int validationM !hasMode(validationMode, MODE_VALIDATE_SEMANTICS)); // check the semantics of the bundle: total sum, semantics per tx (current/last index), missing txs, supply - Validity bundleSemanticsValidity = validateBundleSemantics(startTx, bundleTxsMapping, bundleTxs, validationMode); + Validity bundleSemanticsValidity = validateBundleSemantics(startTx, bundleTxsMapping, bundleTxs, + validationMode); if (hasMode(validationMode, MODE_VALIDATE_SEMANTICS) && bundleSemanticsValidity != Validity.VALID) { return bundleSemanticsValidity; } @@ -177,6 +226,21 @@ public static Validity validate(Tangle tangle, Hash startTxHash, int validationM return bundleHashValidity; } + //verify that the bundle only approves tail txs + if (hasMode(validationMode, MODE_VALIDATE_TAIL_APPROVAL)) { + Validity bundleTailApprovalValidity = validateBundleTailApproval(tangle, bundleTxs); + if (bundleTailApprovalValidity != Validity.VALID) { + return bundleTailApprovalValidity; + } + } + + //verify all transactions within the bundle approve via their branch the trunk transaction of the head transaction + if (hasMode(validationMode, MODE_VALIDATE_BUNDLE_TX_APPROVAL)) { + Validity bundleTransactionsApprovalValidity = validateBundleTransactionsApproval(bundleTxs); + if (bundleTransactionsApprovalValidity != Validity.VALID) { + return bundleTransactionsApprovalValidity; + } + } // verify the signatures of input transactions if (hasMode(validationMode, MODE_VALIDATE_SIGNATURES)) { @@ -198,7 +262,8 @@ public static Validity validate(Tangle tangle, Hash startTxHash, int validationM * @param bundleTxs an empty list which gets filled with the transactions in order of trunk ordering * @return whether the bundle is semantically valid */ - public static Validity validateBundleSemantics(TransactionViewModel startTx, Map bundleTxsMapping, List bundleTxs) { + public Validity validateBundleSemantics(TransactionViewModel startTx, + Map bundleTxsMapping, List bundleTxs) { return validateBundleSemantics(startTx, bundleTxsMapping, bundleTxs, MODE_VALIDATE_SEMANTICS); } @@ -221,7 +286,9 @@ public static Validity validateBundleSemantics(TransactionViewModel startTx, Map * @param validationMode the used validation mode * @return whether the bundle is semantically valid */ - private static Validity validateBundleSemantics(TransactionViewModel startTx, Map bundleTxsMapping, List bundleTxs, int validationMode) { + private Validity validateBundleSemantics(TransactionViewModel startTx, + Map bundleTxsMapping, List bundleTxs, + int validationMode) { TransactionViewModel tvm = startTx; final long lastIndex = tvm.lastIndex(); long bundleValue = 0; @@ -379,6 +446,39 @@ public static boolean isInconsistent(Collection transactio return (sum != 0 || transactionViewModels.isEmpty()); } + /** + * A bundle is invalid if The branch transaction hash of the non head transactions within a bundle, is not the same + * as the trunk transaction hash of the head transaction. + * + * @param bundleTxs list of transactions that are in a bundle. + * @return Whether the bundle tx chain is valid. + */ + @VisibleForTesting + Validity validateBundleTransactionsApproval(List bundleTxs) { + Hash headTrunkTransactionHash = bundleTxs.get(bundleTxs.size() - 1).getTrunkTransactionHash(); + for(int i = 0; i < bundleTxs.size() - 1; i++){ + if(!bundleTxs.get(i).getBranchTransactionHash().equals(headTrunkTransactionHash)){ + return Validity.INVALID; + } + } + return Validity.VALID; + } + + /** + * A bundle is invalid if the trunk and branch transactions approved by the bundle are non tails. + * + * @param bundleTxs The txs in the bundle. + * @return Whether the bundle approves only tails. + */ + @VisibleForTesting + Validity validateBundleTailApproval(Tangle tangle, List bundleTxs) throws Exception { + TransactionViewModel headTx = bundleTxs.get(bundleTxs.size() - 1); + TransactionViewModel bundleTrunkTvm = headTx.getTrunkTransaction(tangle); + TransactionViewModel bundleBranchTvm = headTx.getBranchTransaction(tangle); + return bundleTrunkTvm != null && bundleBranchTvm != null && bundleBranchTvm.getCurrentIndex() == 0 + && bundleTrunkTvm.getCurrentIndex() == 0 ? Validity.VALID : Validity.INVALID; + } + /** * Traverses down the given {@code tail} trunk until all transactions that belong to the same bundle (identified by * the bundle hash) are found and loaded. diff --git a/src/main/java/com/iota/iri/Iota.java b/src/main/java/com/iota/iri/Iota.java index c901fe8fff..f714603842 100644 --- a/src/main/java/com/iota/iri/Iota.java +++ b/src/main/java/com/iota/iri/Iota.java @@ -22,6 +22,7 @@ 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.TransactionValidator; import com.iota.iri.storage.*; import com.iota.iri.storage.rocksDB.RocksDBPersistenceProvider; import com.iota.iri.utils.Pair; diff --git a/src/main/java/com/iota/iri/MainInjectionConfiguration.java b/src/main/java/com/iota/iri/MainInjectionConfiguration.java index e8347fe6f1..f3963bc391 100644 --- a/src/main/java/com/iota/iri/MainInjectionConfiguration.java +++ b/src/main/java/com/iota/iri/MainInjectionConfiguration.java @@ -27,6 +27,7 @@ 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.TransactionValidator; import com.iota.iri.storage.LocalSnapshotsPersistenceProvider; import com.iota.iri.storage.Tangle; import com.iota.iri.storage.rocksDB.RocksDBPersistenceProvider; diff --git a/src/main/java/com/iota/iri/controllers/TransactionViewModel.java b/src/main/java/com/iota/iri/controllers/TransactionViewModel.java index 89e78407ed..207d7cb72d 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; @@ -804,7 +805,7 @@ public static void updateSolidTransactions(Tangle tangle, Snapshot initialSnapsh /** * 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..d393ff606b 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.BundleValidator; +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; @@ -44,11 +45,11 @@ TipsRequester provideTipsRequester(NeighborRouter neighborRouter, Tangle tangle, @Singleton @Provides TransactionProcessingPipeline provideTransactionProcessingPipeline(NeighborRouter neighborRouter, - TransactionValidator txValidator, Tangle tangle, SnapshotProvider snapshotProvider, - TipsViewModel tipsViewModel, LatestMilestoneTracker latestMilestoneTracker, - TransactionRequester transactionRequester) { + TransactionValidator txValidator, Tangle tangle, SnapshotProvider snapshotProvider, + TipsViewModel tipsViewModel, LatestMilestoneTracker latestMilestoneTracker, + TransactionRequester transactionRequester, BundleValidator bundleValidator) { return new TransactionProcessingPipelineImpl(neighborRouter, configuration, txValidator, tangle, - snapshotProvider, tipsViewModel, latestMilestoneTracker, transactionRequester); + snapshotProvider, tipsViewModel, latestMilestoneTracker, transactionRequester, bundleValidator); } @Singleton diff --git a/src/main/java/com/iota/iri/network/pipeline/QuickBundleValidationPayload.java b/src/main/java/com/iota/iri/network/pipeline/QuickBundleValidationPayload.java new file mode 100644 index 0000000000..438ddc31d3 --- /dev/null +++ b/src/main/java/com/iota/iri/network/pipeline/QuickBundleValidationPayload.java @@ -0,0 +1,45 @@ +package com.iota.iri.network.pipeline; + +import com.iota.iri.controllers.TransactionViewModel; +import com.iota.iri.network.neighbor.Neighbor; + +/** + * Defines the payload which gets submitted to the {@link QuickBundleValidationStage} + */ +public class QuickBundleValidationPayload extends Payload { + + private Neighbor neighbor; + private TransactionViewModel tvm; + + /** + * Creates a new {@link QuickBundleValidationPayload} + * + * @param neighbor The {@link Neighbor} form which the transaction originated from. + * @param tvm The transaction + */ + public QuickBundleValidationPayload(Neighbor neighbor, TransactionViewModel tvm) { + this.neighbor = neighbor; + this.tvm = tvm; + } + + @Override + public Neighbor getOriginNeighbor() { + return neighbor; + } + + /** + * Gets the transaction + * + * @return The transaction + */ + public TransactionViewModel getTransactionViewModel() { + return tvm; + } + + @Override + public String toString() { + return "QuickBundleValidationPayload{" + "neighbor=" + neighbor.getHostAddressAndPort() + ", tvm=" + + tvm.getHash() + '}'; + } + +} diff --git a/src/main/java/com/iota/iri/network/pipeline/QuickBundleValidationStage.java b/src/main/java/com/iota/iri/network/pipeline/QuickBundleValidationStage.java new file mode 100644 index 0000000000..a0d678cd2d --- /dev/null +++ b/src/main/java/com/iota/iri/network/pipeline/QuickBundleValidationStage.java @@ -0,0 +1,62 @@ +package com.iota.iri.network.pipeline; + +import com.iota.iri.BundleValidator; +import com.iota.iri.controllers.TransactionViewModel; +import com.iota.iri.service.snapshot.SnapshotProvider; +import com.iota.iri.service.validation.TransactionValidator; +import com.iota.iri.storage.Tangle; + +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link QuickBundleValidationStage} validates the transaction bundle and propagates it if it's valid. + */ +public class QuickBundleValidationStage implements Stage { + + private static final Logger log = LoggerFactory.getLogger(QuickBundleValidationStage.class); + + BundleValidator bundleValidator; + TransactionValidator transactionValidator; + Tangle tangle; + SnapshotProvider snapshotProvider; + + /** + * Creates a new {@link QuickBundleValidationStage} + * + * @param tangle Tangle + * @param snapshotProvider SnapshotProvider + * @param bundleValidator BundleValidator + * @param transactionValidator TransactionValidator + */ + public QuickBundleValidationStage(Tangle tangle, SnapshotProvider snapshotProvider, BundleValidator bundleValidator, + TransactionValidator transactionValidator) { + this.tangle = tangle; + this.snapshotProvider = snapshotProvider; + this.bundleValidator = bundleValidator; + this.transactionValidator = transactionValidator; + } + + @Override + public ProcessingContext process(ProcessingContext ctx) { + QuickBundleValidationPayload payload = (QuickBundleValidationPayload) ctx.getPayload(); + TransactionViewModel tvm = payload.getTransactionViewModel(); + + try { + if (tvm.isSolid() && tvm.getCurrentIndex() == 0) { + List bundleTransactions = bundleValidator.validate(tangle, true, + snapshotProvider.getInitialSnapshot(), tvm.getHash()); + if (!bundleTransactions.isEmpty()) { + transactionValidator.addSolidTransaction(tvm.getHash()); + } + } + } catch (Exception e) { + log.error("error validating bundle", e); + } + ctx.setNextStage(TransactionProcessingPipeline.Stage.BROADCAST); + ctx.setPayload(new BroadcastPayload(payload.getOriginNeighbor(), tvm)); + return ctx; + } +} 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..f91cb6ff46 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.TransactionValidator; import com.iota.iri.controllers.TransactionViewModel; import com.iota.iri.network.TransactionRequester; import com.iota.iri.network.neighbor.Neighbor; @@ -90,9 +90,9 @@ public ProcessingContext process(ProcessingContext ctx) { transactionRequester.removeRecentlyRequestedTransaction(tvm.getHash()); } - // broadcast the newly saved tx to the other neighbors - ctx.setNextStage(TransactionProcessingPipeline.Stage.BROADCAST); - ctx.setPayload(new BroadcastPayload(originNeighbor, tvm)); + // validate the bundle + ctx.setNextStage(TransactionProcessingPipeline.Stage.QUICK_BUNDLE_VALIDATION); + ctx.setPayload(new QuickBundleValidationPayload(originNeighbor, tvm)); return ctx; } } 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..5a553fdc6f 100644 --- a/src/main/java/com/iota/iri/network/pipeline/TransactionProcessingPipeline.java +++ b/src/main/java/com/iota/iri/network/pipeline/TransactionProcessingPipeline.java @@ -14,7 +14,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, QUICK_BUNDLE_VALIDATION, BROADCAST, MULTIPLE, ABORT, FINISH, } /** @@ -111,4 +111,11 @@ enum Stage { * @param hashingStage the {@link HashingStage} to use */ void setHashingStage(HashingStage hashingStage); + + /** + * Sets the quick bundle validation stage. + * + * @param quickBundleValidationStage The {@link QuickBundleValidationStage} to use. + */ + void setQuickBundleValidationStage(QuickBundleValidationStage quickBundleValidationStage); } 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..480d8c389c 100644 --- a/src/main/java/com/iota/iri/network/pipeline/TransactionProcessingPipelineImpl.java +++ b/src/main/java/com/iota/iri/network/pipeline/TransactionProcessingPipelineImpl.java @@ -1,6 +1,7 @@ package com.iota.iri.network.pipeline; -import com.iota.iri.TransactionValidator; +import com.iota.iri.BundleValidator; +import com.iota.iri.service.validation.TransactionValidator; import com.iota.iri.conf.NodeConfig; import com.iota.iri.controllers.TipsViewModel; import com.iota.iri.crypto.batched.BatchedHasher; @@ -69,12 +70,14 @@ public class TransactionProcessingPipelineImpl implements TransactionProcessingP private BroadcastStage broadcastStage; private BatchedHasher batchedHasher; private HashingStage hashingStage; + private QuickBundleValidationStage quickBundleValidationStage; 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 quickBundleValidationStageQueue = new ArrayBlockingQueue<>(100); /** * Creates a {@link TransactionProcessingPipeline}. @@ -89,9 +92,9 @@ public class TransactionProcessingPipelineImpl implements TransactionProcessingP * reply stage */ public TransactionProcessingPipelineImpl(NeighborRouter neighborRouter, NodeConfig config, - TransactionValidator txValidator, Tangle tangle, SnapshotProvider snapshotProvider, - TipsViewModel tipsViewModel, LatestMilestoneTracker latestMilestoneTracker, - TransactionRequester transactionRequester) { + TransactionValidator txValidator, Tangle tangle, SnapshotProvider snapshotProvider, + TipsViewModel tipsViewModel, LatestMilestoneTracker latestMilestoneTracker, + TransactionRequester transactionRequester, BundleValidator bundleValidator) { FIFOCache recentlySeenBytesCache = new FIFOCache<>(config.getCacheSizeBytes()); this.preProcessStage = new PreProcessStage(recentlySeenBytesCache); this.replyStage = new ReplyStage(neighborRouter, config, tangle, tipsViewModel, latestMilestoneTracker, @@ -101,6 +104,7 @@ public TransactionProcessingPipelineImpl(NeighborRouter neighborRouter, NodeConf this.receivedStage = new ReceivedStage(tangle, txValidator, snapshotProvider, transactionRequester); this.batchedHasher = BatchedHasherFactory.create(BatchedHasherFactory.Type.BCTCURL81, 20); this.hashingStage = new HashingStage(batchedHasher); + this.quickBundleValidationStage = new QuickBundleValidationStage(tangle,snapshotProvider,bundleValidator,txValidator); } @Override @@ -111,6 +115,7 @@ public void start() { addStage("reply", replyStageQueue, replyStage); addStage("received", receivedStageQueue, receivedStage); addStage("broadcast", broadcastStageQueue, broadcastStage); + addStage("quickBundleValidation", quickBundleValidationStageQueue, quickBundleValidationStage); } /** @@ -136,6 +141,9 @@ private void addStage(String name, BlockingQueue queue, case RECEIVED: receivedStageQueue.put(ctx); break; + case QUICK_BUNDLE_VALIDATION: + quickBundleValidationStageQueue.put(ctx); + break; case MULTIPLE: MultiStagePayload payload = (MultiStagePayload) ctx.getPayload(); replyStageQueue.put(payload.getLeft()); @@ -255,4 +263,9 @@ public void setBroadcastStage(BroadcastStage broadcastStage) { public void setHashingStage(HashingStage hashingStage) { this.hashingStage = hashingStage; } + + @Override + public void setQuickBundleValidationStage(QuickBundleValidationStage quickBundleValidationStage) { + this.quickBundleValidationStage = quickBundleValidationStage; + } } 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..7d902bf611 100644 --- a/src/main/java/com/iota/iri/service/API.java +++ b/src/main/java/com/iota/iri/service/API.java @@ -6,7 +6,7 @@ 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.conf.APIConfig; import com.iota.iri.conf.IotaConfig; import com.iota.iri.controllers.*; @@ -389,10 +389,14 @@ private AbstractResponse checkConsistencyStatement(List transactionsList state = false; info = "tails are not solid (missing a referenced tx): " + transaction; break; - } else if (bundleValidator.validate(tangle, snapshotProvider.getInitialSnapshot(), txVM.getHash()).isEmpty()) { - state = false; - info = "tails are not consistent (bundle is invalid): " + transaction; - break; + } else { + if (bundleValidator + .validate(tangle, true, snapshotProvider.getInitialSnapshot(), txVM.getHash()) + .isEmpty()) { + state = false; + info = "tails are not consistent (bundle is invalid): " + transaction; + break; + } } } diff --git a/src/main/java/com/iota/iri/service/ledger/LedgerService.java b/src/main/java/com/iota/iri/service/ledger/LedgerService.java index b785cdafc9..c2ac3d9cd0 100644 --- a/src/main/java/com/iota/iri/service/ledger/LedgerService.java +++ b/src/main/java/com/iota/iri/service/ledger/LedgerService.java @@ -2,6 +2,7 @@ import com.iota.iri.controllers.MilestoneViewModel; import com.iota.iri.model.Hash; +import com.iota.iri.storage.Tangle; import java.util.List; import java.util.Map; @@ -99,12 +100,16 @@ public interface LedgerService { *

    * * @param visitedTransactions a set of transaction hashes that shall be considered to be visited already - * @param startTransaction the transaction that marks the start of the dag traversal and that has its approvees - * examined + * @param startTransaction the transaction that marks the start of the dag traversal and that has its approvees + * examined + * @param enforceExtraRules enforce {@link com.iota.iri.BundleValidator#validateBundleTransactionsApproval(List)} + * and {@link com.iota.iri.BundleValidator#validateBundleTailApproval(Tangle, List)}. + * Enforcing them may break backwards compatibility. * @return a map of the balance changes (addresses associated to their balance) or {@code null} if the balance could * not be generated due to inconsistencies * @throws LedgerException if anything unexpected happens while generating the balance changes */ - Map generateBalanceDiff(Set visitedTransactions, Hash startTransaction, int milestoneIndex) + Map generateBalanceDiff(Set visitedTransactions, Hash startTransaction, int milestoneIndex, + boolean enforceExtraRules) throws LedgerException; } diff --git a/src/main/java/com/iota/iri/service/ledger/impl/LedgerServiceImpl.java b/src/main/java/com/iota/iri/service/ledger/impl/LedgerServiceImpl.java index da0224e62d..d340236551 100644 --- a/src/main/java/com/iota/iri/service/ledger/impl/LedgerServiceImpl.java +++ b/src/main/java/com/iota/iri/service/ledger/impl/LedgerServiceImpl.java @@ -133,7 +133,7 @@ public boolean isBalanceDiffConsistent(Set approvedHashes, Map } Set visitedHashes = new HashSet<>(approvedHashes); Map currentState = generateBalanceDiff(visitedHashes, tip, - snapshotProvider.getLatestSnapshot().getIndex()); + snapshotProvider.getLatestSnapshot().getIndex(), true); if (currentState == null) { return false; } @@ -152,7 +152,8 @@ public boolean isBalanceDiffConsistent(Set approvedHashes, Map } @Override - public Map generateBalanceDiff(Set visitedTransactions, Hash startTransaction, int milestoneIndex) + public Map generateBalanceDiff(Set visitedTransactions, Hash startTransaction, int milestoneIndex, + boolean enforceExtraRules) throws LedgerException { Map state = new HashMap<>(); @@ -176,7 +177,8 @@ public Map generateBalanceDiff(Set visitedTransactions, Hash s if (!milestoneService.isTransactionConfirmed(transactionViewModel, milestoneIndex)) { final List bundleTransactions = bundleValidator.validate(tangle, - snapshotProvider.getInitialSnapshot(), transactionViewModel.getHash()); + enforceExtraRules, snapshotProvider.getInitialSnapshot(), + transactionViewModel.getHash()); if (bundleTransactions.isEmpty()) { return null; @@ -257,7 +259,7 @@ private boolean generateStateDiff(MilestoneViewModel milestone) throws LedgerExc try { Hash tail = transactionViewModel.getHash(); Map balanceChanges = generateBalanceDiff(new HashSet<>(), tail, - snapshotProvider.getLatestSnapshot().getIndex()); + snapshotProvider.getLatestSnapshot().getIndex(), false); successfullyProcessed = balanceChanges != null; if (successfullyProcessed) { successfullyProcessed = snapshotProvider.getLatestSnapshot().patchedState( diff --git a/src/main/java/com/iota/iri/service/milestone/impl/MilestoneServiceImpl.java b/src/main/java/com/iota/iri/service/milestone/impl/MilestoneServiceImpl.java index a6146a8062..6d3188f10b 100644 --- a/src/main/java/com/iota/iri/service/milestone/impl/MilestoneServiceImpl.java +++ b/src/main/java/com/iota/iri/service/milestone/impl/MilestoneServiceImpl.java @@ -162,7 +162,7 @@ public MilestoneValidity validateMilestone(TransactionViewModel transactionViewM return existingMilestone.getHash().equals(transactionViewModel.getHash()) ? VALID : INVALID; } - final List bundleTransactions = bundleValidator.validate(tangle, + final List bundleTransactions = bundleValidator.validate(tangle, true, snapshotProvider.getInitialSnapshot(), transactionViewModel.getHash()); if (bundleTransactions.isEmpty()) { 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..4b2079fd07 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.TransactionValidator; import com.iota.iri.model.Hash; import com.iota.iri.service.milestone.MilestoneSolidifier; import com.iota.iri.service.snapshot.SnapshotProvider; diff --git a/src/main/java/com/iota/iri/service/spentaddresses/impl/SpentAddressesServiceImpl.java b/src/main/java/com/iota/iri/service/spentaddresses/impl/SpentAddressesServiceImpl.java index 9c2cf55e22..4766caab11 100644 --- a/src/main/java/com/iota/iri/service/spentaddresses/impl/SpentAddressesServiceImpl.java +++ b/src/main/java/com/iota/iri/service/spentaddresses/impl/SpentAddressesServiceImpl.java @@ -125,7 +125,7 @@ private boolean wasTransactionSpentFrom(TransactionViewModel tx) throws Exceptio private boolean isBundleValid(Hash tailHash) throws Exception { List validation = - bundleValidator.validate(tangle, snapshotProvider.getInitialSnapshot(), tailHash); + bundleValidator.validate(tangle, false, snapshotProvider.getInitialSnapshot(), tailHash); return (CollectionUtils.isNotEmpty(validation) && validation.get(0).getValidity() == 1); } diff --git a/src/main/java/com/iota/iri/TransactionValidator.java b/src/main/java/com/iota/iri/service/validation/TransactionValidator.java similarity index 98% rename from src/main/java/com/iota/iri/TransactionValidator.java rename to src/main/java/com/iota/iri/service/validation/TransactionValidator.java index ef51b5c203..b2c4995c70 100644 --- a/src/main/java/com/iota/iri/TransactionValidator.java +++ b/src/main/java/com/iota/iri/service/validation/TransactionValidator.java @@ -1,4 +1,4 @@ -package com.iota.iri; +package com.iota.iri.service.validation; import com.google.common.annotations.VisibleForTesting; import com.iota.iri.conf.ProtocolConfig; @@ -65,7 +65,7 @@ public class TransactionValidator { * 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) { + public TransactionValidator(Tangle tangle, SnapshotProvider snapshotProvider, TipsViewModel tipsViewModel, TransactionRequester transactionRequester, ProtocolConfig protocolConfig) { this.tangle = tangle; this.snapshotProvider = snapshotProvider; this.tipsViewModel = tipsViewModel; diff --git a/src/test/java/com/iota/iri/BundleValidatorTest.java b/src/test/java/com/iota/iri/BundleValidatorTest.java index a9cd40622c..97e453627b 100644 --- a/src/test/java/com/iota/iri/BundleValidatorTest.java +++ b/src/test/java/com/iota/iri/BundleValidatorTest.java @@ -4,6 +4,7 @@ import com.iota.iri.crypto.ISS; import com.iota.iri.crypto.SpongeFactory; import com.iota.iri.model.Hash; +import com.iota.iri.model.HashFactory; import com.iota.iri.model.TransactionHash; import com.iota.iri.service.snapshot.Snapshot; import com.iota.iri.service.snapshot.impl.SnapshotMockUtils; @@ -13,6 +14,10 @@ import com.iota.iri.controllers.TransactionViewModel; import org.junit.*; import org.junit.rules.TemporaryFolder; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; import java.util.*; import java.util.stream.Collectors; @@ -20,9 +25,15 @@ import static org.junit.Assert.*; public class BundleValidatorTest { + + @Rule + public MockitoRule mockitoRule = MockitoJUnit.rule(); + private static Tangle tangle = new Tangle(); private static Snapshot snapshot; - private static BundleValidator bundleValidator = new BundleValidator(); + + @Spy + private BundleValidator bundleValidator = new BundleValidator(); @Before public void setUp() throws Exception { @@ -79,6 +90,80 @@ public void checkInconsistencyOfConsistentBundle() { assertFalse("should be false because bundle is consistent", BundleValidator.isInconsistent(transactions)); } + @Test + public void checkBundleTransactionsApproval() { + //tx0 and tx1 both approve via branch, the trunk of tx2 + String[] trytes = { + "999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999GNXKWQPHMFCEZZCKUCCD9PROQVPCTBVVAPHCLVOPYYQLQRMPGTQXBVZLKGGT9NXHUAASFIAVEOZTBSM9DA99999999999999999999999999RENGLE9BEAT9999999999999999FDKW9AD99999999999B99999999RJPQGE9AXNBBLPQ9IFJXFJTXJAIGDDXAWXGAVU9PJQNYFADJDIDBTJKSAEBBX9NWJGZVAVXYWXPCEFFH9DKZYSMJAQRLXZHCNPTJPJLOONNDZHGNTBIINGYKZQAAWF9LWQTHUQSAXMLQN9DKCQWHIJXNGLGQVA9999XIFVJWCABR9XQKOWCXJAZDZWMWWBVHROBOPISAWDIJTSFPVAUZNJUMNUCOFIBIAAVIOWDTCNIPRVA9999TANGLE9BEAT9999999999999999SVIDBWNOF999999999K99999999POWSRVIO9VMIJ99UGNETMNPNNNE", + "YU9WINMHSBIJ9HRBKDEHZYOCDGOZ9HZSSVXAYYOD9AMAFBCROMLSFDFIAQKEXAVFDLJYVABRSONSTTXLCQJTSDRFDEXZIORLRSQDZQODNCHAAFUPGBCEVYNRSJQGUZRAQIIEZPFQGWPQIBMDVNZGQAKCU9VLHGYSNDPHCMOCTRYQOYHFPRQSGHSDEF9MSHGXYGUTHSBSF9SOSJQFOZXMQGCUXAYWLBJHXOHTOM9ITZPISEXLAYAZBHTZDDCOIHKDBL9AAXZEDFHDYCPXLDGR9JTVXDXBC9DRDVREEEVGVPWVEZHJBYQYTGDEWQHVEDTAVLWW9WBXOIGDGUAAAVKFMPVKSYBVOIWPYGCMACEAAHGUIQVFLFMSCPKTYQWJDGMDNLMDIUUYI9SZOFQPLKKACESSWIXPFYZWDHETYUGEVASPHBGQPJPWVESSUUEVUYDYNQUQURSMCEPKIHYXNFHZSHIIDTMEZQ9IPSHTBDK9UHRSIIEJRILBCMVUMGAGBMHKEMUUKQDCYAVIAN9MBWJCAUSSJRSAUEUBGEFFCFSOBVGLXBAIDOH9NXWQUSIFUINEVZUWELLVIOSSQRVHMTDBCATBWFSDJJDLURAUKEQEIRICSCXAOJNPWHQKYVTRLCRKHLFHHHPYSMUEAPMQPMPS9CHCIYXEYRNIOOGRWKVHUGIWJZIGPWGZGBGYTVC9UVEJ9JBIYMDHYXLWIDXHZVFCCPLYXDPQPJGMKNZMMONGNXSNNPCGEGRJTMCPTJONGPAEDMSHMGAPGBDWVCSCTJAECSHTEGBMVCDZMQYZ9IHERWGCKAIMFOJRAEDRDSWJASV9XYS9CTOEETPQHRKJZ9FPWTVCGLEJQJXVVDTYZ9DNSHQAINKROQQIPTFGTABEELYPBQHGNNB9NCL9BVTG9MH99BPIRNNGC9MQQRH9YUDYSUJIDSZKCIGEKFSJ9PXUYKVPUZYFIREUAMUZE9EIMJK9YGAZXZBPRXIRDCEVDHWOCFIJTVRCRZRRJGFLILKVNMJAX9MUMTXCWTUTP9NXBZUMAYYY9DNVZTCROBCMMHVKSQRXMGROCMMCDENWQWBV9LEIJONWTQXOUQZOTGEJHJELYZOPICLDWPHVCMBFSYHPIJYZQDEGYITVPYNQMXPRTPKADLUKW9AEMSMGIWCUMOQMUDN9HZGYKTNVVNLOQAFVETEDJMJVKYCE9RE9AJP9RMYFKGDEC9GIGAFXEXIYXPFBOMRPULVJMYTQEPDMMIABDHJGRNNCKADZAUFV9WCWFYFHXLCED9E9SKKYHVKDTGNCHEKFJL9YJHTSRUVKEPHPCQSFNBXKOJNGBSEOZLVVGNUQTRACVGEKDSORUHVSDSQNUZASWXRADGAUDYDPODDEDNDDT9OAEARSBLPVSYAOEYYZJIZIZDENQOKMXATCTD9NEFVQWNCET9N9IXWCCWHYUBDTDJUZFXWMATRYSGJUQAFKAPIQIIXVFFEFNLGDPAIGKONUWLUTQTKZJYDEGPASTOMM9QTGOMYHFUKMNEVWPCMTLPDSOEEWDBRHZPZJIFMBUTY9QPRLPSVURIRMCWLH9YIZBOTJQMMF9ZP9VCCEGWVKSSQNANP9ZXVCAFGAVBXDJHUMKTKUBZRNKUGJTVUPDZMUK9KZI9EKY9DQDUNA9ZVPCROKYYPISSTDOJZJXMXZWSGZBEPKAUYPBPUTDVQKUYXWYTZXLYTCLLKLJUUJGFFCMZFKDTJET9KEDRMWZSAEFCSXLMRHRLG9EKLRZJ9NZQEONGFQSKSQMWRZXWVEGHCF9ZKGQITDLGFTZLCQMXVGVYIPKSUPPAOIKVWARJHAEJUZOOJFCFJTGQZWROSCEQSITQWZTQDFKECATBWSIRDFVWH9TXXUVEBRRKOTFSGBMGCMZDK9BJRVEUYOVNSLZNNSTWOB9CFNDJHJENDWSTIMMSWD9HSA9LDGTJDJFRGQCWOHYJNMFFLLDMVQEMCMNVQWMOGCEZUTK9MPULTDMKTIVFNVWXDNTRHNREHEDWIZCCRFVM9TXCTLBLYEKTRVVWHKZDOSYOBNLEKPNXMDZGLSVBBNBJIBZYBXZCENKVIXMNGUJUYFGFYFBKELSBZROQWKMESZTWDEEUCSXOWFSNJUGGJANMJFCBJV9GMDDJCAJHWKYQFJLFWZESTSIRBZQWYHGQDFUUWF9F9HXSBZRKBDLRWXSMKW99KPWJOQYPMHB9KWALSVLFNANYSMNWSJLUOCBKAWAJHNWMLPLXHVEPTKBPWLWHMDZ99999999999999999999999999999999999999999999999999999FDKW9AD99A99999999B99999999RJPQGE9AXNBBLPQ9IFJXFJTXJAIGDDXAWXGAVU9PJQNYFADJDIDBTJKSAEBBX9NWJGZVAVXYWXPCEFFH99XJLIVAZYJGPKEHANESX9DORLEIOIFL9WVQBBNUDRZDGJMVSRTSFKOJPIMEXPBCPGKUUEKRGAXOB99999XIFVJWCABR9XQKOWCXJAZDZWMWWBVHROBOPISAWDIJTSFPVAUZNJUMNUCOFIBIAAVIOWDTCNIPRVA9999999999999999999999999999999RSIDBWNOF999999999K99999999POWSRVIO9VMIJ99FKKNKGPNNNNP", + "COSNPEACSPESTKTUS9CSMOCWVUBLERQJQGLLA9AYRRXMOVOBULQZZZJPLNELEX9EVZDAFOHBB9FBAYOTDB9YGJAVTDWNPJVVGEPFJ9QBYLXAYZEETY9GMYQRIRAIPWMJUFMFRSQRCEIOWISMBJKGCIGIIYNMXAYLXZIBNKANT9DSQXPBUPWGUJTOMD9JKSNFZJJFBZYBWRWQIZFOZXQWNJTRZTUPHVEGTAOLMBEPXOB9TCEBNIWXQUHNQJZHAOWGVOOOTSCTEUEMXJUODZP9CDLCMEXAW9YJGVNREENETEYXOAKZVRJVUSCRUSRCNZUGUOLCNHQITWYPJVJGPVULASTONDZRDVCNGJSN9DMMUORCOIBTITSPBXSAVTOHSAXHMGH99APRTQIVCEI9NWYKDOZCCWBNZGGCUCISSMGJHJ9OVSFBZVFMBODKRKSLIDESGFDQITCYFARWYTLFKSPLNVQ9LTYFODCHBAJKCWVZNBLOJH9ABFGDFOXHDNHZILINMYMVZYWTFJJOLADPTZRPJEMGPFQNYTFDGOKMDTDSKPTHIDWLMRSSACZMTHUCWVZHCYSPJRATABZ9JHWEBGTTQORGFOYDWUIYFYGOGGTYGYYI9RPKPMCTYNYKHHNWQKJTDQDHTRUYQPMCDDLVUUHUBPWGPMNQYPKHDHGIUSXJGNUXIYGZRGVYPTTV9KXJDLGVVXRIWIGKZJVCQ9DUWAYNDRKYCGKZDEOKZQCXELSQXLGLAPBDIWRTKGSIRNAZ9XFEGMWOWLPUA9TSGYRDOU9GKY9JJDKCJKKGJCRARGSNECJ9SEVAGPPJMBTMGNWSLNSFBOEYMIQBKOVSCTDIWCOQTHBVTZOFNNIYKKJLAROKIRFFPCLSOWO9BVNNJSXEYLJASPUEFKCEI9SGWADNGS9MZAKFTCAYNRVKGDIGYDTPGJXWFAEFGG9BX9XCNBZGQULH9IPPIQ9VFVAXGELUMCTE9TOFOGCTFYBYEBFOKKAESIIHEWLYLQKWDHPYNELJKMZHDSDSK9ZWFP99FQFLUXGTSVDMGHIFZFUCYTYPVEFEDM9TOHCQVIBXARYWZEKTWEOVSBYZTJZRUFZJCLRBTOWPTKY99CYQTGPMAKFYALIWPEQLUB9RGANTKDDMSDSJOTNPFZCXKJUGTJWWPK9FAJGPJOEBRHL9ZU9XRBDJBVMFIZIZFGKETH9RJUSDIOWDZIXFOJLT9DSIAWSCEYBNOGOAZQPZKVZTBXGACCBVQGFNGXNQFTVPUGUOHNBKTUERLSTDXGRKZSATIRHDBSXARYATBHOLUHCQGPRMQGSJUAXUCWKDXAUGORHFVVVDZQEYYGSV9IUM9VTMHJCIKBQD9TQLLYTZHAKDHPFGYMRGJOEDJKQTBBNFCKDEPYTPWUJEOPRJWHAILMVW9AXS9HAT9GCGRXZEHWNDOVGKGPWFXFC9IKLVZJCTGZUJFRHGL9FZWZYP9TPOWGOUTQ9INYBVTD9N9OV9AM9GYRJ99MRWRWLOAKJXMDTFNS9NZQAYGVZRJ9WVSIDRRQLGKH9VPUVKFWZMSRVVLKXUGJAZESC9LEYDNNDWS9WTWNFPCR9YKPOMIJAVHCRCJVBDFWSTDN9BPJRDMGZFXWTY9GUX9WEPJQMXSECFPREXAJ9DU9OLMNQFTF99QYZOGDUTMBHMNMCNUQTU9FTNEUNBT9NQO9YADGY9C9LQR9DYVJZE9GMMYAOUSWUIVJ9EAR9EEFCO9SZTQ9JQHPIJGXIOQZHOSUSVMHBOLTYSEONVLRISNAN9KJHHFFISTYIHYVAMXLPNALDELETXVSWGUWOIVKNAKZKNNRVNJKE9WMNQBXFGXSYZQQSIODAKINA9CPBOGAYKJ9V9GS9HERXSVYPEILYXC9GURBREBCXRZJVXHUOPGYFLPIFBCOTMHFTAAUQUZVEKOWYFFGSCXDFDBTSGZRMPQR9EYSZYGQETCWFDRXVOXXCUBOQTBQGVLAONSTSLURFYSDPAVNCXNWVPQOUORMVSLYISQUQKXFYYMJSGHQP9VAAZABEAGJMCPIEBZQSYCPS9CNKFBDRDTDA9C9MVKGUISRX9ODVUJNPDWMZ9ESYXJ99ETDGSTJZMZYJEGRUWTORMCLNHLDQ9YIJWPMDYGGJQHMFPJ9VKINJUESTCBJQXFTCJFNUXPZONVKXJRAYGKIWHXSBZRKBDLRWXSMKW99KPWJOQYPMHB9KWALSVLFNANYSMNWSJLUOCBKAWAJHNWMLPLXHVEPTKBPWLWHMD999999999999999999999999999999999999999999999999999999FDKW9AD99B99999999B99999999RJPQGE9AXNBBLPQ9IFJXFJTXJAIGDDXAWXGAVU9PJQNYFADJDIDBTJKSAEBBX9NWJGZVAVXYWXPCEFFH9XIFVJWCABR9XQKOWCXJAZDZWMWWBVHROBOPISAWDIJTSFPVAUZNJUMNUCOFIBIAAVIOWDTCNIPRVA9999FTXZAEQIKGFJABOGCCQGOMKNASGWCQWMNIMRDTHKPJMTKDGMEQNZOAYARIYEWFZYJALRKFODOITTZ9999999999999999999999999999999LLHDBWNOF999999999K99999999POWSRVIO9VMIJ99UMGMTTTPNNNV", + }; + List transactions = persistAndMapTxs(trytes); + assertEquals("should be valid", BundleValidator.Validity.VALID, + bundleValidator.validateBundleTransactionsApproval(transactions)); + } + + @Test + public void checkBundleTransactionsApprovalFailure(){ + //tx1 approves via branch, the branch of tx2 + String[] trytes = { + "999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999GNXKWQPHMFCEZZCKUCCD9PROQVPCTBVVAPHCLVOPYYQLQRMPGTQXBVZLKGGT9NXHUAASFIAVEOZTBSM9DA99999999999999999999999999RENGLE9BEAT9999999999999999FDKW9AD99999999999B99999999RJPQGE9AXNBBLPQ9IFJXFJTXJAIGDDXAWXGAVU9PJQNYFADJDIDBTJKSAEBBX9NWJGZVAVXYWXPCEFFH9DKZYSMJAQRLXZHCNPTJPJLOONNDZHGNTBIINGYKZQAAWF9LWQTHUQSAXMLQN9DKCQWHIJXNGLGQVA9999XIFVJWCABR9XQKOWCXJAZDZWMWWBVHROBOPISAWDIJTSFPVAUZNJUMNUCOFIBIAAVIOWDTCNIPRVA9999TANGLE9BEAT9999999999999999SVIDBWNOF999999999K99999999POWSRVIO9VMIJ99UGNETMNPNNNE", + "YU9WINMHSBIJ9HRBKDEHZYOCDGOZ9HZSSVXAYYOD9AMAFBCROMLSFDFIAQKEXAVFDLJYVABRSONSTTXLCQJTSDRFDEXZIORLRSQDZQODNCHAAFUPGBCEVYNRSJQGUZRAQIIEZPFQGWPQIBMDVNZGQAKCU9VLHGYSNDPHCMOCTRYQOYHFPRQSGHSDEF9MSHGXYGUTHSBSF9SOSJQFOZXMQGCUXAYWLBJHXOHTOM9ITZPISEXLAYAZBHTZDDCOIHKDBL9AAXZEDFHDYCPXLDGR9JTVXDXBC9DRDVREEEVGVPWVEZHJBYQYTGDEWQHVEDTAVLWW9WBXOIGDGUAAAVKFMPVKSYBVOIWPYGCMACEAAHGUIQVFLFMSCPKTYQWJDGMDNLMDIUUYI9SZOFQPLKKACESSWIXPFYZWDHETYUGEVASPHBGQPJPWVESSUUEVUYDYNQUQURSMCEPKIHYXNFHZSHIIDTMEZQ9IPSHTBDK9UHRSIIEJRILBCMVUMGAGBMHKEMUUKQDCYAVIAN9MBWJCAUSSJRSAUEUBGEFFCFSOBVGLXBAIDOH9NXWQUSIFUINEVZUWELLVIOSSQRVHMTDBCATBWFSDJJDLURAUKEQEIRICSCXAOJNPWHQKYVTRLCRKHLFHHHPYSMUEAPMQPMPS9CHCIYXEYRNIOOGRWKVHUGIWJZIGPWGZGBGYTVC9UVEJ9JBIYMDHYXLWIDXHZVFCCPLYXDPQPJGMKNZMMONGNXSNNPCGEGRJTMCPTJONGPAEDMSHMGAPGBDWVCSCTJAECSHTEGBMVCDZMQYZ9IHERWGCKAIMFOJRAEDRDSWJASV9XYS9CTOEETPQHRKJZ9FPWTVCGLEJQJXVVDTYZ9DNSHQAINKROQQIPTFGTABEELYPBQHGNNB9NCL9BVTG9MH99BPIRNNGC9MQQRH9YUDYSUJIDSZKCIGEKFSJ9PXUYKVPUZYFIREUAMUZE9EIMJK9YGAZXZBPRXIRDCEVDHWOCFIJTVRCRZRRJGFLILKVNMJAX9MUMTXCWTUTP9NXBZUMAYYY9DNVZTCROBCMMHVKSQRXMGROCMMCDENWQWBV9LEIJONWTQXOUQZOTGEJHJELYZOPICLDWPHVCMBFSYHPIJYZQDEGYITVPYNQMXPRTPKADLUKW9AEMSMGIWCUMOQMUDN9HZGYKTNVVNLOQAFVETEDJMJVKYCE9RE9AJP9RMYFKGDEC9GIGAFXEXIYXPFBOMRPULVJMYTQEPDMMIABDHJGRNNCKADZAUFV9WCWFYFHXLCED9E9SKKYHVKDTGNCHEKFJL9YJHTSRUVKEPHPCQSFNBXKOJNGBSEOZLVVGNUQTRACVGEKDSORUHVSDSQNUZASWXRADGAUDYDPODDEDNDDT9OAEARSBLPVSYAOEYYZJIZIZDENQOKMXATCTD9NEFVQWNCET9N9IXWCCWHYUBDTDJUZFXWMATRYSGJUQAFKAPIQIIXVFFEFNLGDPAIGKONUWLUTQTKZJYDEGPASTOMM9QTGOMYHFUKMNEVWPCMTLPDSOEEWDBRHZPZJIFMBUTY9QPRLPSVURIRMCWLH9YIZBOTJQMMF9ZP9VCCEGWVKSSQNANP9ZXVCAFGAVBXDJHUMKTKUBZRNKUGJTVUPDZMUK9KZI9EKY9DQDUNA9ZVPCROKYYPISSTDOJZJXMXZWSGZBEPKAUYPBPUTDVQKUYXWYTZXLYTCLLKLJUUJGFFCMZFKDTJET9KEDRMWZSAEFCSXLMRHRLG9EKLRZJ9NZQEONGFQSKSQMWRZXWVEGHCF9ZKGQITDLGFTZLCQMXVGVYIPKSUPPAOIKVWARJHAEJUZOOJFCFJTGQZWROSCEQSITQWZTQDFKECATBWSIRDFVWH9TXXUVEBRRKOTFSGBMGCMZDK9BJRVEUYOVNSLZNNSTWOB9CFNDJHJENDWSTIMMSWD9HSA9LDGTJDJFRGQCWOHYJNMFFLLDMVQEMCMNVQWMOGCEZUTK9MPULTDMKTIVFNVWXDNTRHNREHEDWIZCCRFVM9TXCTLBLYEKTRVVWHKZDOSYOBNLEKPNXMDZGLSVBBNBJIBZYBXZCENKVIXMNGUJUYFGFYFBKELSBZROQWKMESZTWDEEUCSXOWFSNJUGGJANMJFCBJV9GMDDJCAJHWKYQFJLFWZESTSIRBZQWYHGQDFUUWF9F9HXSBZRKBDLRWXSMKW99KPWJOQYPMHB9KWALSVLFNANYSMNWSJLUOCBKAWAJHNWMLPLXHVEPTKBPWLWHMDZ99999999999999999999999999999999999999999999999999999FDKW9AD99A99999999B99999999RJPQGE9AXNBBLPQ9IFJXFJTXJAIGDDXAWXGAVU9PJQNYFADJDIDBTJKSAEBBX9NWJGZVAVXYWXPCEFFH99XJLIVAZYJGPKEHANESX9DORLEIOIFL9WVQBBNUDRZDGJMVSRTSFKOJPIMEXPBCPGKUUEKRGAXOB99999FTXZAEQIKGFJABOGCCQGOMKNASGWCQWMNIMRDTHKPJMTKDGMEQNZOAYARIYEWFZYJALRKFODOITTZ9999999999999999999999999999999RSIDBWNOF999999999K99999999POWSRVIO9VMIJ99FKKNKGPNNNNP", + "COSNPEACSPESTKTUS9CSMOCWVUBLERQJQGLLA9AYRRXMOVOBULQZZZJPLNELEX9EVZDAFOHBB9FBAYOTDB9YGJAVTDWNPJVVGEPFJ9QBYLXAYZEETY9GMYQRIRAIPWMJUFMFRSQRCEIOWISMBJKGCIGIIYNMXAYLXZIBNKANT9DSQXPBUPWGUJTOMD9JKSNFZJJFBZYBWRWQIZFOZXQWNJTRZTUPHVEGTAOLMBEPXOB9TCEBNIWXQUHNQJZHAOWGVOOOTSCTEUEMXJUODZP9CDLCMEXAW9YJGVNREENETEYXOAKZVRJVUSCRUSRCNZUGUOLCNHQITWYPJVJGPVULASTONDZRDVCNGJSN9DMMUORCOIBTITSPBXSAVTOHSAXHMGH99APRTQIVCEI9NWYKDOZCCWBNZGGCUCISSMGJHJ9OVSFBZVFMBODKRKSLIDESGFDQITCYFARWYTLFKSPLNVQ9LTYFODCHBAJKCWVZNBLOJH9ABFGDFOXHDNHZILINMYMVZYWTFJJOLADPTZRPJEMGPFQNYTFDGOKMDTDSKPTHIDWLMRSSACZMTHUCWVZHCYSPJRATABZ9JHWEBGTTQORGFOYDWUIYFYGOGGTYGYYI9RPKPMCTYNYKHHNWQKJTDQDHTRUYQPMCDDLVUUHUBPWGPMNQYPKHDHGIUSXJGNUXIYGZRGVYPTTV9KXJDLGVVXRIWIGKZJVCQ9DUWAYNDRKYCGKZDEOKZQCXELSQXLGLAPBDIWRTKGSIRNAZ9XFEGMWOWLPUA9TSGYRDOU9GKY9JJDKCJKKGJCRARGSNECJ9SEVAGPPJMBTMGNWSLNSFBOEYMIQBKOVSCTDIWCOQTHBVTZOFNNIYKKJLAROKIRFFPCLSOWO9BVNNJSXEYLJASPUEFKCEI9SGWADNGS9MZAKFTCAYNRVKGDIGYDTPGJXWFAEFGG9BX9XCNBZGQULH9IPPIQ9VFVAXGELUMCTE9TOFOGCTFYBYEBFOKKAESIIHEWLYLQKWDHPYNELJKMZHDSDSK9ZWFP99FQFLUXGTSVDMGHIFZFUCYTYPVEFEDM9TOHCQVIBXARYWZEKTWEOVSBYZTJZRUFZJCLRBTOWPTKY99CYQTGPMAKFYALIWPEQLUB9RGANTKDDMSDSJOTNPFZCXKJUGTJWWPK9FAJGPJOEBRHL9ZU9XRBDJBVMFIZIZFGKETH9RJUSDIOWDZIXFOJLT9DSIAWSCEYBNOGOAZQPZKVZTBXGACCBVQGFNGXNQFTVPUGUOHNBKTUERLSTDXGRKZSATIRHDBSXARYATBHOLUHCQGPRMQGSJUAXUCWKDXAUGORHFVVVDZQEYYGSV9IUM9VTMHJCIKBQD9TQLLYTZHAKDHPFGYMRGJOEDJKQTBBNFCKDEPYTPWUJEOPRJWHAILMVW9AXS9HAT9GCGRXZEHWNDOVGKGPWFXFC9IKLVZJCTGZUJFRHGL9FZWZYP9TPOWGOUTQ9INYBVTD9N9OV9AM9GYRJ99MRWRWLOAKJXMDTFNS9NZQAYGVZRJ9WVSIDRRQLGKH9VPUVKFWZMSRVVLKXUGJAZESC9LEYDNNDWS9WTWNFPCR9YKPOMIJAVHCRCJVBDFWSTDN9BPJRDMGZFXWTY9GUX9WEPJQMXSECFPREXAJ9DU9OLMNQFTF99QYZOGDUTMBHMNMCNUQTU9FTNEUNBT9NQO9YADGY9C9LQR9DYVJZE9GMMYAOUSWUIVJ9EAR9EEFCO9SZTQ9JQHPIJGXIOQZHOSUSVMHBOLTYSEONVLRISNAN9KJHHFFISTYIHYVAMXLPNALDELETXVSWGUWOIVKNAKZKNNRVNJKE9WMNQBXFGXSYZQQSIODAKINA9CPBOGAYKJ9V9GS9HERXSVYPEILYXC9GURBREBCXRZJVXHUOPGYFLPIFBCOTMHFTAAUQUZVEKOWYFFGSCXDFDBTSGZRMPQR9EYSZYGQETCWFDRXVOXXCUBOQTBQGVLAONSTSLURFYSDPAVNCXNWVPQOUORMVSLYISQUQKXFYYMJSGHQP9VAAZABEAGJMCPIEBZQSYCPS9CNKFBDRDTDA9C9MVKGUISRX9ODVUJNPDWMZ9ESYXJ99ETDGSTJZMZYJEGRUWTORMCLNHLDQ9YIJWPMDYGGJQHMFPJ9VKINJUESTCBJQXFTCJFNUXPZONVKXJRAYGKIWHXSBZRKBDLRWXSMKW99KPWJOQYPMHB9KWALSVLFNANYSMNWSJLUOCBKAWAJHNWMLPLXHVEPTKBPWLWHMD999999999999999999999999999999999999999999999999999999FDKW9AD99B99999999B99999999RJPQGE9AXNBBLPQ9IFJXFJTXJAIGDDXAWXGAVU9PJQNYFADJDIDBTJKSAEBBX9NWJGZVAVXYWXPCEFFH9XIFVJWCABR9XQKOWCXJAZDZWMWWBVHROBOPISAWDIJTSFPVAUZNJUMNUCOFIBIAAVIOWDTCNIPRVA9999FTXZAEQIKGFJABOGCCQGOMKNASGWCQWMNIMRDTHKPJMTKDGMEQNZOAYARIYEWFZYJALRKFODOITTZ9999999999999999999999999999999LLHDBWNOF999999999K99999999POWSRVIO9VMIJ99UMGMTTTPNNNV", + }; + List transactions = persistAndMapTxs(trytes); + assertEquals("should be invalid", BundleValidator.Validity.INVALID, + bundleValidator.validateBundleTransactionsApproval(transactions)); + } + + @Test + public void checkBundleTailApproval() throws Exception { + //tx2 approves via branch and trunk, tail txs + String[] trytes = { + "999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999GNXKWQPHMFCEZZCKUCCD9PROQVPCTBVVAPHCLVOPYYQLQRMPGTQXBVZLKGGT9NXHUAASFIAVEOZTBSM9DA99999999999999999999999999RENGLE9BEAT9999999999999999FDKW9AD99999999999B99999999RJPQGE9AXNBBLPQ9IFJXFJTXJAIGDDXAWXGAVU9PJQNYFADJDIDBTJKSAEBBX9NWJGZVAVXYWXPCEFFH9DKZYSMJAQRLXZHCNPTJPJLOONNDZHGNTBIINGYKZQAAWF9LWQTHUQSAXMLQN9DKCQWHIJXNGLGQVA9999XIFVJWCABR9XQKOWCXJAZDZWMWWBVHROBOPISAWDIJTSFPVAUZNJUMNUCOFIBIAAVIOWDTCNIPRVA9999TANGLE9BEAT9999999999999999SVIDBWNOF999999999K99999999POWSRVIO9VMIJ99UGNETMNPNNNE", + "YU9WINMHSBIJ9HRBKDEHZYOCDGOZ9HZSSVXAYYOD9AMAFBCROMLSFDFIAQKEXAVFDLJYVABRSONSTTXLCQJTSDRFDEXZIORLRSQDZQODNCHAAFUPGBCEVYNRSJQGUZRAQIIEZPFQGWPQIBMDVNZGQAKCU9VLHGYSNDPHCMOCTRYQOYHFPRQSGHSDEF9MSHGXYGUTHSBSF9SOSJQFOZXMQGCUXAYWLBJHXOHTOM9ITZPISEXLAYAZBHTZDDCOIHKDBL9AAXZEDFHDYCPXLDGR9JTVXDXBC9DRDVREEEVGVPWVEZHJBYQYTGDEWQHVEDTAVLWW9WBXOIGDGUAAAVKFMPVKSYBVOIWPYGCMACEAAHGUIQVFLFMSCPKTYQWJDGMDNLMDIUUYI9SZOFQPLKKACESSWIXPFYZWDHETYUGEVASPHBGQPJPWVESSUUEVUYDYNQUQURSMCEPKIHYXNFHZSHIIDTMEZQ9IPSHTBDK9UHRSIIEJRILBCMVUMGAGBMHKEMUUKQDCYAVIAN9MBWJCAUSSJRSAUEUBGEFFCFSOBVGLXBAIDOH9NXWQUSIFUINEVZUWELLVIOSSQRVHMTDBCATBWFSDJJDLURAUKEQEIRICSCXAOJNPWHQKYVTRLCRKHLFHHHPYSMUEAPMQPMPS9CHCIYXEYRNIOOGRWKVHUGIWJZIGPWGZGBGYTVC9UVEJ9JBIYMDHYXLWIDXHZVFCCPLYXDPQPJGMKNZMMONGNXSNNPCGEGRJTMCPTJONGPAEDMSHMGAPGBDWVCSCTJAECSHTEGBMVCDZMQYZ9IHERWGCKAIMFOJRAEDRDSWJASV9XYS9CTOEETPQHRKJZ9FPWTVCGLEJQJXVVDTYZ9DNSHQAINKROQQIPTFGTABEELYPBQHGNNB9NCL9BVTG9MH99BPIRNNGC9MQQRH9YUDYSUJIDSZKCIGEKFSJ9PXUYKVPUZYFIREUAMUZE9EIMJK9YGAZXZBPRXIRDCEVDHWOCFIJTVRCRZRRJGFLILKVNMJAX9MUMTXCWTUTP9NXBZUMAYYY9DNVZTCROBCMMHVKSQRXMGROCMMCDENWQWBV9LEIJONWTQXOUQZOTGEJHJELYZOPICLDWPHVCMBFSYHPIJYZQDEGYITVPYNQMXPRTPKADLUKW9AEMSMGIWCUMOQMUDN9HZGYKTNVVNLOQAFVETEDJMJVKYCE9RE9AJP9RMYFKGDEC9GIGAFXEXIYXPFBOMRPULVJMYTQEPDMMIABDHJGRNNCKADZAUFV9WCWFYFHXLCED9E9SKKYHVKDTGNCHEKFJL9YJHTSRUVKEPHPCQSFNBXKOJNGBSEOZLVVGNUQTRACVGEKDSORUHVSDSQNUZASWXRADGAUDYDPODDEDNDDT9OAEARSBLPVSYAOEYYZJIZIZDENQOKMXATCTD9NEFVQWNCET9N9IXWCCWHYUBDTDJUZFXWMATRYSGJUQAFKAPIQIIXVFFEFNLGDPAIGKONUWLUTQTKZJYDEGPASTOMM9QTGOMYHFUKMNEVWPCMTLPDSOEEWDBRHZPZJIFMBUTY9QPRLPSVURIRMCWLH9YIZBOTJQMMF9ZP9VCCEGWVKSSQNANP9ZXVCAFGAVBXDJHUMKTKUBZRNKUGJTVUPDZMUK9KZI9EKY9DQDUNA9ZVPCROKYYPISSTDOJZJXMXZWSGZBEPKAUYPBPUTDVQKUYXWYTZXLYTCLLKLJUUJGFFCMZFKDTJET9KEDRMWZSAEFCSXLMRHRLG9EKLRZJ9NZQEONGFQSKSQMWRZXWVEGHCF9ZKGQITDLGFTZLCQMXVGVYIPKSUPPAOIKVWARJHAEJUZOOJFCFJTGQZWROSCEQSITQWZTQDFKECATBWSIRDFVWH9TXXUVEBRRKOTFSGBMGCMZDK9BJRVEUYOVNSLZNNSTWOB9CFNDJHJENDWSTIMMSWD9HSA9LDGTJDJFRGQCWOHYJNMFFLLDMVQEMCMNVQWMOGCEZUTK9MPULTDMKTIVFNVWXDNTRHNREHEDWIZCCRFVM9TXCTLBLYEKTRVVWHKZDOSYOBNLEKPNXMDZGLSVBBNBJIBZYBXZCENKVIXMNGUJUYFGFYFBKELSBZROQWKMESZTWDEEUCSXOWFSNJUGGJANMJFCBJV9GMDDJCAJHWKYQFJLFWZESTSIRBZQWYHGQDFUUWF9F9HXSBZRKBDLRWXSMKW99KPWJOQYPMHB9KWALSVLFNANYSMNWSJLUOCBKAWAJHNWMLPLXHVEPTKBPWLWHMDZ99999999999999999999999999999999999999999999999999999FDKW9AD99A99999999B99999999RJPQGE9AXNBBLPQ9IFJXFJTXJAIGDDXAWXGAVU9PJQNYFADJDIDBTJKSAEBBX9NWJGZVAVXYWXPCEFFH99XJLIVAZYJGPKEHANESX9DORLEIOIFL9WVQBBNUDRZDGJMVSRTSFKOJPIMEXPBCPGKUUEKRGAXOB99999XIFVJWCABR9XQKOWCXJAZDZWMWWBVHROBOPISAWDIJTSFPVAUZNJUMNUCOFIBIAAVIOWDTCNIPRVA9999999999999999999999999999999RSIDBWNOF999999999K99999999POWSRVIO9VMIJ99FKKNKGPNNNNP", + "COSNPEACSPESTKTUS9CSMOCWVUBLERQJQGLLA9AYRRXMOVOBULQZZZJPLNELEX9EVZDAFOHBB9FBAYOTDB9YGJAVTDWNPJVVGEPFJ9QBYLXAYZEETY9GMYQRIRAIPWMJUFMFRSQRCEIOWISMBJKGCIGIIYNMXAYLXZIBNKANT9DSQXPBUPWGUJTOMD9JKSNFZJJFBZYBWRWQIZFOZXQWNJTRZTUPHVEGTAOLMBEPXOB9TCEBNIWXQUHNQJZHAOWGVOOOTSCTEUEMXJUODZP9CDLCMEXAW9YJGVNREENETEYXOAKZVRJVUSCRUSRCNZUGUOLCNHQITWYPJVJGPVULASTONDZRDVCNGJSN9DMMUORCOIBTITSPBXSAVTOHSAXHMGH99APRTQIVCEI9NWYKDOZCCWBNZGGCUCISSMGJHJ9OVSFBZVFMBODKRKSLIDESGFDQITCYFARWYTLFKSPLNVQ9LTYFODCHBAJKCWVZNBLOJH9ABFGDFOXHDNHZILINMYMVZYWTFJJOLADPTZRPJEMGPFQNYTFDGOKMDTDSKPTHIDWLMRSSACZMTHUCWVZHCYSPJRATABZ9JHWEBGTTQORGFOYDWUIYFYGOGGTYGYYI9RPKPMCTYNYKHHNWQKJTDQDHTRUYQPMCDDLVUUHUBPWGPMNQYPKHDHGIUSXJGNUXIYGZRGVYPTTV9KXJDLGVVXRIWIGKZJVCQ9DUWAYNDRKYCGKZDEOKZQCXELSQXLGLAPBDIWRTKGSIRNAZ9XFEGMWOWLPUA9TSGYRDOU9GKY9JJDKCJKKGJCRARGSNECJ9SEVAGPPJMBTMGNWSLNSFBOEYMIQBKOVSCTDIWCOQTHBVTZOFNNIYKKJLAROKIRFFPCLSOWO9BVNNJSXEYLJASPUEFKCEI9SGWADNGS9MZAKFTCAYNRVKGDIGYDTPGJXWFAEFGG9BX9XCNBZGQULH9IPPIQ9VFVAXGELUMCTE9TOFOGCTFYBYEBFOKKAESIIHEWLYLQKWDHPYNELJKMZHDSDSK9ZWFP99FQFLUXGTSVDMGHIFZFUCYTYPVEFEDM9TOHCQVIBXARYWZEKTWEOVSBYZTJZRUFZJCLRBTOWPTKY99CYQTGPMAKFYALIWPEQLUB9RGANTKDDMSDSJOTNPFZCXKJUGTJWWPK9FAJGPJOEBRHL9ZU9XRBDJBVMFIZIZFGKETH9RJUSDIOWDZIXFOJLT9DSIAWSCEYBNOGOAZQPZKVZTBXGACCBVQGFNGXNQFTVPUGUOHNBKTUERLSTDXGRKZSATIRHDBSXARYATBHOLUHCQGPRMQGSJUAXUCWKDXAUGORHFVVVDZQEYYGSV9IUM9VTMHJCIKBQD9TQLLYTZHAKDHPFGYMRGJOEDJKQTBBNFCKDEPYTPWUJEOPRJWHAILMVW9AXS9HAT9GCGRXZEHWNDOVGKGPWFXFC9IKLVZJCTGZUJFRHGL9FZWZYP9TPOWGOUTQ9INYBVTD9N9OV9AM9GYRJ99MRWRWLOAKJXMDTFNS9NZQAYGVZRJ9WVSIDRRQLGKH9VPUVKFWZMSRVVLKXUGJAZESC9LEYDNNDWS9WTWNFPCR9YKPOMIJAVHCRCJVBDFWSTDN9BPJRDMGZFXWTY9GUX9WEPJQMXSECFPREXAJ9DU9OLMNQFTF99QYZOGDUTMBHMNMCNUQTU9FTNEUNBT9NQO9YADGY9C9LQR9DYVJZE9GMMYAOUSWUIVJ9EAR9EEFCO9SZTQ9JQHPIJGXIOQZHOSUSVMHBOLTYSEONVLRISNAN9KJHHFFISTYIHYVAMXLPNALDELETXVSWGUWOIVKNAKZKNNRVNJKE9WMNQBXFGXSYZQQSIODAKINA9CPBOGAYKJ9V9GS9HERXSVYPEILYXC9GURBREBCXRZJVXHUOPGYFLPIFBCOTMHFTAAUQUZVEKOWYFFGSCXDFDBTSGZRMPQR9EYSZYGQETCWFDRXVOXXCUBOQTBQGVLAONSTSLURFYSDPAVNCXNWVPQOUORMVSLYISQUQKXFYYMJSGHQP9VAAZABEAGJMCPIEBZQSYCPS9CNKFBDRDTDA9C9MVKGUISRX9ODVUJNPDWMZ9ESYXJ99ETDGSTJZMZYJEGRUWTORMCLNHLDQ9YIJWPMDYGGJQHMFPJ9VKINJUESTCBJQXFTCJFNUXPZONVKXJRAYGKIWHXSBZRKBDLRWXSMKW99KPWJOQYPMHB9KWALSVLFNANYSMNWSJLUOCBKAWAJHNWMLPLXHVEPTKBPWLWHMD999999999999999999999999999999999999999999999999999999FDKW9AD99B99999999B99999999RJPQGE9AXNBBLPQ9IFJXFJTXJAIGDDXAWXGAVU9PJQNYFADJDIDBTJKSAEBBX9NWJGZVAVXYWXPCEFFH9XIFVJWCABR9XQKOWCXJAZDZWMWWBVHROBOPISAWDIJTSFPVAUZNJUMNUCOFIBIAAVIOWDTCNIPRVA9999FTXZAEQIKGFJABOGCCQGOMKNASGWCQWMNIMRDTHKPJMTKDGMEQNZOAYARIYEWFZYJALRKFODOITTZ9999999999999999999999999999999LLHDBWNOF999999999K99999999POWSRVIO9VMIJ99UMGMTTTPNNNV", + }; + List transactions = persistAndMapTxs(trytes); + assertEquals("should be valid because bundle approves tails", BundleValidator.Validity.VALID, + bundleValidator.validateBundleTailApproval(tangle, transactions)); + } + + @Test + public void checkBundleNonTailApproval() throws Exception { + //tx with currentIndex = 1 + String[] nonTailTrytes = {"YU9WINMHSBIJ9HRBKDEHZYOCDGOZ9HZSSVXAYYOD9AMAFBCROMLSFDFIAQKEXAVFDLJYVABRSONSTTXLCQJTSDRFDEXZIORLRSQDZQODNCHAAFUPGBCEVYNRSJQGUZRAQIIEZPFQGWPQIBMDVNZGQAKCU9VLHGYSNDPHCMOCTRYQOYHFPRQSGHSDEF9MSHGXYGUTHSBSF9SOSJQFOZXMQGCUXAYWLBJHXOHTOM9ITZPISEXLAYAZBHTZDDCOIHKDBL9AAXZEDFHDYCPXLDGR9JTVXDXBC9DRDVREEEVGVPWVEZHJBYQYTGDEWQHVEDTAVLWW9WBXOIGDGUAAAVKFMPVKSYBVOIWPYGCMACEAAHGUIQVFLFMSCPKTYQWJDGMDNLMDIUUYI9SZOFQPLKKACESSWIXPFYZWDHETYUGEVASPHBGQPJPWVESSUUEVUYDYNQUQURSMCEPKIHYXNFHZSHIIDTMEZQ9IPSHTBDK9UHRSIIEJRILBCMVUMGAGBMHKEMUUKQDCYAVIAN9MBWJCAUSSJRSAUEUBGEFFCFSOBVGLXBAIDOH9NXWQUSIFUINEVZUWELLVIOSSQRVHMTDBCATBWFSDJJDLURAUKEQEIRICSCXAOJNPWHQKYVTRLCRKHLFHHHPYSMUEAPMQPMPS9CHCIYXEYRNIOOGRWKVHUGIWJZIGPWGZGBGYTVC9UVEJ9JBIYMDHYXLWIDXHZVFCCPLYXDPQPJGMKNZMMONGNXSNNPCGEGRJTMCPTJONGPAEDMSHMGAPGBDWVCSCTJAECSHTEGBMVCDZMQYZ9IHERWGCKAIMFOJRAEDRDSWJASV9XYS9CTOEETPQHRKJZ9FPWTVCGLEJQJXVVDTYZ9DNSHQAINKROQQIPTFGTABEELYPBQHGNNB9NCL9BVTG9MH99BPIRNNGC9MQQRH9YUDYSUJIDSZKCIGEKFSJ9PXUYKVPUZYFIREUAMUZE9EIMJK9YGAZXZBPRXIRDCEVDHWOCFIJTVRCRZRRJGFLILKVNMJAX9MUMTXCWTUTP9NXBZUMAYYY9DNVZTCROBCMMHVKSQRXMGROCMMCDENWQWBV9LEIJONWTQXOUQZOTGEJHJELYZOPICLDWPHVCMBFSYHPIJYZQDEGYITVPYNQMXPRTPKADLUKW9AEMSMGIWCUMOQMUDN9HZGYKTNVVNLOQAFVETEDJMJVKYCE9RE9AJP9RMYFKGDEC9GIGAFXEXIYXPFBOMRPULVJMYTQEPDMMIABDHJGRNNCKADZAUFV9WCWFYFHXLCED9E9SKKYHVKDTGNCHEKFJL9YJHTSRUVKEPHPCQSFNBXKOJNGBSEOZLVVGNUQTRACVGEKDSORUHVSDSQNUZASWXRADGAUDYDPODDEDNDDT9OAEARSBLPVSYAOEYYZJIZIZDENQOKMXATCTD9NEFVQWNCET9N9IXWCCWHYUBDTDJUZFXWMATRYSGJUQAFKAPIQIIXVFFEFNLGDPAIGKONUWLUTQTKZJYDEGPASTOMM9QTGOMYHFUKMNEVWPCMTLPDSOEEWDBRHZPZJIFMBUTY9QPRLPSVURIRMCWLH9YIZBOTJQMMF9ZP9VCCEGWVKSSQNANP9ZXVCAFGAVBXDJHUMKTKUBZRNKUGJTVUPDZMUK9KZI9EKY9DQDUNA9ZVPCROKYYPISSTDOJZJXMXZWSGZBEPKAUYPBPUTDVQKUYXWYTZXLYTCLLKLJUUJGFFCMZFKDTJET9KEDRMWZSAEFCSXLMRHRLG9EKLRZJ9NZQEONGFQSKSQMWRZXWVEGHCF9ZKGQITDLGFTZLCQMXVGVYIPKSUPPAOIKVWARJHAEJUZOOJFCFJTGQZWROSCEQSITQWZTQDFKECATBWSIRDFVWH9TXXUVEBRRKOTFSGBMGCMZDK9BJRVEUYOVNSLZNNSTWOB9CFNDJHJENDWSTIMMSWD9HSA9LDGTJDJFRGQCWOHYJNMFFLLDMVQEMCMNVQWMOGCEZUTK9MPULTDMKTIVFNVWXDNTRHNREHEDWIZCCRFVM9TXCTLBLYEKTRVVWHKZDOSYOBNLEKPNXMDZGLSVBBNBJIBZYBXZCENKVIXMNGUJUYFGFYFBKELSBZROQWKMESZTWDEEUCSXOWFSNJUGGJANMJFCBJV9GMDDJCAJHWKYQFJLFWZESTSIRBZQWYHGQDFUUWF9F9HXSBZRKBDLRWXSMKW99KPWJOQYPMHB9KWALSVLFNANYSMNWSJLUOCBKAWAJHNWMLPLXHVEPTKBPWLWHMDZ99999999999999999999999999999999999999999999999999999FDKW9AD99A99999999B99999999RJPQGE9AXNBBLPQ9IFJXFJTXJAIGDDXAWXGAVU9PJQNYFADJDIDBTJKSAEBBX9NWJGZVAVXYWXPCEFFH99XJLIVAZYJGPKEHANESX9DORLEIOIFL9WVQBBNUDRZDGJMVSRTSFKOJPIMEXPBCPGKUUEKRGAXOB99999XIFVJWCABR9XQKOWCXJAZDZWMWWBVHROBOPISAWDIJTSFPVAUZNJUMNUCOFIBIAAVIOWDTCNIPRVA9999999999999999999999999999999RSIDBWNOF999999999K99999999POWSRVIO9VMIJ99FKKNKGPNNNNP"}; + + //approves via head branch, non-tail tx + String[] trytes = { + "999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999GNXKWQPHMFCEZZCKUCCD9PROQVPCTBVVAPHCLVOPYYQLQRMPGTQXBVZLKGGT9NXHUAASFIAVEOZTBSM9DA99999999999999999999999999RENGLE9BEAT9999999999999999FDKW9AD99999999999B99999999ABCDGE9AXNBBLPQ9IFJXFJTXJAIGDDXAWXGAVU9PJQNYFADJDIDBTJKSAEBBX9NWJGZVAVXYWXPCEFFH9DKZYSMJAQRLXZHCNPTJPJLOONNDZHGNTBIINGYKZQAAWF9LWQTHUQSAXMLQN9DKCQWHIJXNGLGQVA9999XIFVJWCABR9XQKOWCXJAZDZWMWWBVHROBOPISAWDIJTSFPVAUZNJUMNUCOFIBIAAVIOWDTCNIPRVA9999TANGLE9BEAT9999999999999999SVIDBWNOF999999999K99999999POWSRVIO9VMIJ99UGNETMNPNNNE", + "YU9WINMHSBIJ9HRBKDEHZYOCDGOZ9HZSSVXAYYOD9AMAFBCROMLSFDFIAQKEXAVFDLJYVABRSONSTTXLCQJTSDRFDEXZIORLRSQDZQODNCHAAFUPGBCEVYNRSJQGUZRAQIIEZPFQGWPQIBMDVNZGQAKCU9VLHGYSNDPHCMOCTRYQOYHFPRQSGHSDEF9MSHGXYGUTHSBSF9SOSJQFOZXMQGCUXAYWLBJHXOHTOM9ITZPISEXLAYAZBHTZDDCOIHKDBL9AAXZEDFHDYCPXLDGR9JTVXDXBC9DRDVREEEVGVPWVEZHJBYQYTGDEWQHVEDTAVLWW9WBXOIGDGUAAAVKFMPVKSYBVOIWPYGCMACEAAHGUIQVFLFMSCPKTYQWJDGMDNLMDIUUYI9SZOFQPLKKACESSWIXPFYZWDHETYUGEVASPHBGQPJPWVESSUUEVUYDYNQUQURSMCEPKIHYXNFHZSHIIDTMEZQ9IPSHTBDK9UHRSIIEJRILBCMVUMGAGBMHKEMUUKQDCYAVIAN9MBWJCAUSSJRSAUEUBGEFFCFSOBVGLXBAIDOH9NXWQUSIFUINEVZUWELLVIOSSQRVHMTDBCATBWFSDJJDLURAUKEQEIRICSCXAOJNPWHQKYVTRLCRKHLFHHHPYSMUEAPMQPMPS9CHCIYXEYRNIOOGRWKVHUGIWJZIGPWGZGBGYTVC9UVEJ9JBIYMDHYXLWIDXHZVFCCPLYXDPQPJGMKNZMMONGNXSNNPCGEGRJTMCPTJONGPAEDMSHMGAPGBDWVCSCTJAECSHTEGBMVCDZMQYZ9IHERWGCKAIMFOJRAEDRDSWJASV9XYS9CTOEETPQHRKJZ9FPWTVCGLEJQJXVVDTYZ9DNSHQAINKROQQIPTFGTABEELYPBQHGNNB9NCL9BVTG9MH99BPIRNNGC9MQQRH9YUDYSUJIDSZKCIGEKFSJ9PXUYKVPUZYFIREUAMUZE9EIMJK9YGAZXZBPRXIRDCEVDHWOCFIJTVRCRZRRJGFLILKVNMJAX9MUMTXCWTUTP9NXBZUMAYYY9DNVZTCROBCMMHVKSQRXMGROCMMCDENWQWBV9LEIJONWTQXOUQZOTGEJHJELYZOPICLDWPHVCMBFSYHPIJYZQDEGYITVPYNQMXPRTPKADLUKW9AEMSMGIWCUMOQMUDN9HZGYKTNVVNLOQAFVETEDJMJVKYCE9RE9AJP9RMYFKGDEC9GIGAFXEXIYXPFBOMRPULVJMYTQEPDMMIABDHJGRNNCKADZAUFV9WCWFYFHXLCED9E9SKKYHVKDTGNCHEKFJL9YJHTSRUVKEPHPCQSFNBXKOJNGBSEOZLVVGNUQTRACVGEKDSORUHVSDSQNUZASWXRADGAUDYDPODDEDNDDT9OAEARSBLPVSYAOEYYZJIZIZDENQOKMXATCTD9NEFVQWNCET9N9IXWCCWHYUBDTDJUZFXWMATRYSGJUQAFKAPIQIIXVFFEFNLGDPAIGKONUWLUTQTKZJYDEGPASTOMM9QTGOMYHFUKMNEVWPCMTLPDSOEEWDBRHZPZJIFMBUTY9QPRLPSVURIRMCWLH9YIZBOTJQMMF9ZP9VCCEGWVKSSQNANP9ZXVCAFGAVBXDJHUMKTKUBZRNKUGJTVUPDZMUK9KZI9EKY9DQDUNA9ZVPCROKYYPISSTDOJZJXMXZWSGZBEPKAUYPBPUTDVQKUYXWYTZXLYTCLLKLJUUJGFFCMZFKDTJET9KEDRMWZSAEFCSXLMRHRLG9EKLRZJ9NZQEONGFQSKSQMWRZXWVEGHCF9ZKGQITDLGFTZLCQMXVGVYIPKSUPPAOIKVWARJHAEJUZOOJFCFJTGQZWROSCEQSITQWZTQDFKECATBWSIRDFVWH9TXXUVEBRRKOTFSGBMGCMZDK9BJRVEUYOVNSLZNNSTWOB9CFNDJHJENDWSTIMMSWD9HSA9LDGTJDJFRGQCWOHYJNMFFLLDMVQEMCMNVQWMOGCEZUTK9MPULTDMKTIVFNVWXDNTRHNREHEDWIZCCRFVM9TXCTLBLYEKTRVVWHKZDOSYOBNLEKPNXMDZGLSVBBNBJIBZYBXZCENKVIXMNGUJUYFGFYFBKELSBZROQWKMESZTWDEEUCSXOWFSNJUGGJANMJFCBJV9GMDDJCAJHWKYQFJLFWZESTSIRBZQWYHGQDFUUWF9F9HXSBZRKBDLRWXSMKW99KPWJOQYPMHB9KWALSVLFNANYSMNWSJLUOCBKAWAJHNWMLPLXHVEPTKBPWLWHMDZ99999999999999999999999999999999999999999999999999999FDKW9AD99A99999999B99999999ABCDGE9AXNBBLPQ9IFJXFJTXJAIGDDXAWXGAVU9PJQNYFADJDIDBTJKSAEBBX9NWJGZVAVXYWXPCEFFH99XJLIVAZYJGPKEHANESX9DORLEIOIFL9WVQBBNUDRZDGJMVSRTSFKOJPIMEXPBCPGKUUEKRGAXOB99999XIFVJWCABR9XQKOWCXJAZDZWMWWBVHROBOPISAWDIJTSFPVAUZNJUMNUCOFIBIAAVIOWDTCNIPRVA9999999999999999999999999999999RSIDBWNOF999999999K99999999POWSRVIO9VMIJ99FKKNKGPNNNNP", + "COSNPEACSPESTKTUS9CSMOCWVUBLERQJQGLLA9AYRRXMOVOBULQZZZJPLNELEX9EVZDAFOHBB9FBAYOTDB9YGJAVTDWNPJVVGEPFJ9QBYLXAYZEETY9GMYQRIRAIPWMJUFMFRSQRCEIOWISMBJKGCIGIIYNMXAYLXZIBNKANT9DSQXPBUPWGUJTOMD9JKSNFZJJFBZYBWRWQIZFOZXQWNJTRZTUPHVEGTAOLMBEPXOB9TCEBNIWXQUHNQJZHAOWGVOOOTSCTEUEMXJUODZP9CDLCMEXAW9YJGVNREENETEYXOAKZVRJVUSCRUSRCNZUGUOLCNHQITWYPJVJGPVULASTONDZRDVCNGJSN9DMMUORCOIBTITSPBXSAVTOHSAXHMGH99APRTQIVCEI9NWYKDOZCCWBNZGGCUCISSMGJHJ9OVSFBZVFMBODKRKSLIDESGFDQITCYFARWYTLFKSPLNVQ9LTYFODCHBAJKCWVZNBLOJH9ABFGDFOXHDNHZILINMYMVZYWTFJJOLADPTZRPJEMGPFQNYTFDGOKMDTDSKPTHIDWLMRSSACZMTHUCWVZHCYSPJRATABZ9JHWEBGTTQORGFOYDWUIYFYGOGGTYGYYI9RPKPMCTYNYKHHNWQKJTDQDHTRUYQPMCDDLVUUHUBPWGPMNQYPKHDHGIUSXJGNUXIYGZRGVYPTTV9KXJDLGVVXRIWIGKZJVCQ9DUWAYNDRKYCGKZDEOKZQCXELSQXLGLAPBDIWRTKGSIRNAZ9XFEGMWOWLPUA9TSGYRDOU9GKY9JJDKCJKKGJCRARGSNECJ9SEVAGPPJMBTMGNWSLNSFBOEYMIQBKOVSCTDIWCOQTHBVTZOFNNIYKKJLAROKIRFFPCLSOWO9BVNNJSXEYLJASPUEFKCEI9SGWADNGS9MZAKFTCAYNRVKGDIGYDTPGJXWFAEFGG9BX9XCNBZGQULH9IPPIQ9VFVAXGELUMCTE9TOFOGCTFYBYEBFOKKAESIIHEWLYLQKWDHPYNELJKMZHDSDSK9ZWFP99FQFLUXGTSVDMGHIFZFUCYTYPVEFEDM9TOHCQVIBXARYWZEKTWEOVSBYZTJZRUFZJCLRBTOWPTKY99CYQTGPMAKFYALIWPEQLUB9RGANTKDDMSDSJOTNPFZCXKJUGTJWWPK9FAJGPJOEBRHL9ZU9XRBDJBVMFIZIZFGKETH9RJUSDIOWDZIXFOJLT9DSIAWSCEYBNOGOAZQPZKVZTBXGACCBVQGFNGXNQFTVPUGUOHNBKTUERLSTDXGRKZSATIRHDBSXARYATBHOLUHCQGPRMQGSJUAXUCWKDXAUGORHFVVVDZQEYYGSV9IUM9VTMHJCIKBQD9TQLLYTZHAKDHPFGYMRGJOEDJKQTBBNFCKDEPYTPWUJEOPRJWHAILMVW9AXS9HAT9GCGRXZEHWNDOVGKGPWFXFC9IKLVZJCTGZUJFRHGL9FZWZYP9TPOWGOUTQ9INYBVTD9N9OV9AM9GYRJ99MRWRWLOAKJXMDTFNS9NZQAYGVZRJ9WVSIDRRQLGKH9VPUVKFWZMSRVVLKXUGJAZESC9LEYDNNDWS9WTWNFPCR9YKPOMIJAVHCRCJVBDFWSTDN9BPJRDMGZFXWTY9GUX9WEPJQMXSECFPREXAJ9DU9OLMNQFTF99QYZOGDUTMBHMNMCNUQTU9FTNEUNBT9NQO9YADGY9C9LQR9DYVJZE9GMMYAOUSWUIVJ9EAR9EEFCO9SZTQ9JQHPIJGXIOQZHOSUSVMHBOLTYSEONVLRISNAN9KJHHFFISTYIHYVAMXLPNALDELETXVSWGUWOIVKNAKZKNNRVNJKE9WMNQBXFGXSYZQQSIODAKINA9CPBOGAYKJ9V9GS9HERXSVYPEILYXC9GURBREBCXRZJVXHUOPGYFLPIFBCOTMHFTAAUQUZVEKOWYFFGSCXDFDBTSGZRMPQR9EYSZYGQETCWFDRXVOXXCUBOQTBQGVLAONSTSLURFYSDPAVNCXNWVPQOUORMVSLYISQUQKXFYYMJSGHQP9VAAZABEAGJMCPIEBZQSYCPS9CNKFBDRDTDA9C9MVKGUISRX9ODVUJNPDWMZ9ESYXJ99ETDGSTJZMZYJEGRUWTORMCLNHLDQ9YIJWPMDYGGJQHMFPJ9VKINJUESTCBJQXFTCJFNUXPZONVKXJRAYGKIWHXSBZRKBDLRWXSMKW99KPWJOQYPMHB9KWALSVLFNANYSMNWSJLUOCBKAWAJHNWMLPLXHVEPTKBPWLWHMD999999999999999999999999999999999999999999999999999999FDKW9AD99B99999999B99999999ABCDGE9AXNBBLPQ9IFJXFJTXJAIGDDXAWXGAVU9PJQNYFADJDIDBTJKSAEBBX9NWJGZVAVXYWXPCEFFH9XIFVJWCABR9XQKOWCXJAZDZWMWWBVHROBOPISAWDIJTSFPVAUZNJUMNUCOFIBIAAVIOWDTCNIPRVA9999DKZYSMJAQRLXZHCNPTJPJLOONNDZHGNTBIINGYKZQAAWF9LWQTHUQSAXMLQN9DKCQWHIJXNGLGQVA9999999999999999999999999999999LLHDBWNOF999999999K99999999POWSRVIO9VMIJ99UMGMTTTPNNNV", + }; + persistAndMapTxs(nonTailTrytes); + List transactions = persistAndMapTxs(trytes); + assertEquals("should be invalid because bundle approves non-tails", BundleValidator.Validity.INVALID, + bundleValidator.validateBundleTailApproval(tangle, transactions)); + } + + @Test + public void validateBundleNonTailApprovalFailure() throws Exception { + // tx with currentIndex = 1 + TransactionViewModel nonTail = TransactionTestUtils.createBundleHead(1, HashFactory.TRANSACTION + .create("XIFVJWCABR9XQKOWCXJAZDZWMWWBVHROBOPISAWDIJTSFPVAUZNJUMNUCOFIBIAAVIOWDTCNIPRVA9999")); + + // approves via head branch, non-tail tx + String[] trytes = { + "999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999GNXKWQPHMFCEZZCKUCCD9PROQVPCTBVVAPHCLVOPYYQLQRMPGTQXBVZLKGGT9NXHUAASFIAVEOZTBSM9DA99999999999999999999999999RENGLE9BEAT9999999999999999FDKW9AD99999999999B99999999RJPQGE9AXNBBLPQ9IFJXFJTXJAIGDDXAWXGAVU9PJQNYFADJDIDBTJKSAEBBX9NWJGZVAVXYWXPCEFFH9DKZYSMJAQRLXZHCNPTJPJLOONNDZHGNTBIINGYKZQAAWF9LWQTHUQSAXMLQN9DKCQWHIJXNGLGQVA9999XIFVJWCABR9XQKOWCXJAZDZWMWWBVHROBOPISAWDIJTSFPVAUZNJUMNUCOFIBIAAVIOWDTCNIPRVA9999TANGLE9BEAT9999999999999999SVIDBWNOF999999999K99999999POWSRVIO9VMIJ99UGNETMNPNNNE", + "YU9WINMHSBIJ9HRBKDEHZYOCDGOZ9HZSSVXAYYOD9AMAFBCROMLSFDFIAQKEXAVFDLJYVABRSONSTTXLCQJTSDRFDEXZIORLRSQDZQODNCHAAFUPGBCEVYNRSJQGUZRAQIIEZPFQGWPQIBMDVNZGQAKCU9VLHGYSNDPHCMOCTRYQOYHFPRQSGHSDEF9MSHGXYGUTHSBSF9SOSJQFOZXMQGCUXAYWLBJHXOHTOM9ITZPISEXLAYAZBHTZDDCOIHKDBL9AAXZEDFHDYCPXLDGR9JTVXDXBC9DRDVREEEVGVPWVEZHJBYQYTGDEWQHVEDTAVLWW9WBXOIGDGUAAAVKFMPVKSYBVOIWPYGCMACEAAHGUIQVFLFMSCPKTYQWJDGMDNLMDIUUYI9SZOFQPLKKACESSWIXPFYZWDHETYUGEVASPHBGQPJPWVESSUUEVUYDYNQUQURSMCEPKIHYXNFHZSHIIDTMEZQ9IPSHTBDK9UHRSIIEJRILBCMVUMGAGBMHKEMUUKQDCYAVIAN9MBWJCAUSSJRSAUEUBGEFFCFSOBVGLXBAIDOH9NXWQUSIFUINEVZUWELLVIOSSQRVHMTDBCATBWFSDJJDLURAUKEQEIRICSCXAOJNPWHQKYVTRLCRKHLFHHHPYSMUEAPMQPMPS9CHCIYXEYRNIOOGRWKVHUGIWJZIGPWGZGBGYTVC9UVEJ9JBIYMDHYXLWIDXHZVFCCPLYXDPQPJGMKNZMMONGNXSNNPCGEGRJTMCPTJONGPAEDMSHMGAPGBDWVCSCTJAECSHTEGBMVCDZMQYZ9IHERWGCKAIMFOJRAEDRDSWJASV9XYS9CTOEETPQHRKJZ9FPWTVCGLEJQJXVVDTYZ9DNSHQAINKROQQIPTFGTABEELYPBQHGNNB9NCL9BVTG9MH99BPIRNNGC9MQQRH9YUDYSUJIDSZKCIGEKFSJ9PXUYKVPUZYFIREUAMUZE9EIMJK9YGAZXZBPRXIRDCEVDHWOCFIJTVRCRZRRJGFLILKVNMJAX9MUMTXCWTUTP9NXBZUMAYYY9DNVZTCROBCMMHVKSQRXMGROCMMCDENWQWBV9LEIJONWTQXOUQZOTGEJHJELYZOPICLDWPHVCMBFSYHPIJYZQDEGYITVPYNQMXPRTPKADLUKW9AEMSMGIWCUMOQMUDN9HZGYKTNVVNLOQAFVETEDJMJVKYCE9RE9AJP9RMYFKGDEC9GIGAFXEXIYXPFBOMRPULVJMYTQEPDMMIABDHJGRNNCKADZAUFV9WCWFYFHXLCED9E9SKKYHVKDTGNCHEKFJL9YJHTSRUVKEPHPCQSFNBXKOJNGBSEOZLVVGNUQTRACVGEKDSORUHVSDSQNUZASWXRADGAUDYDPODDEDNDDT9OAEARSBLPVSYAOEYYZJIZIZDENQOKMXATCTD9NEFVQWNCET9N9IXWCCWHYUBDTDJUZFXWMATRYSGJUQAFKAPIQIIXVFFEFNLGDPAIGKONUWLUTQTKZJYDEGPASTOMM9QTGOMYHFUKMNEVWPCMTLPDSOEEWDBRHZPZJIFMBUTY9QPRLPSVURIRMCWLH9YIZBOTJQMMF9ZP9VCCEGWVKSSQNANP9ZXVCAFGAVBXDJHUMKTKUBZRNKUGJTVUPDZMUK9KZI9EKY9DQDUNA9ZVPCROKYYPISSTDOJZJXMXZWSGZBEPKAUYPBPUTDVQKUYXWYTZXLYTCLLKLJUUJGFFCMZFKDTJET9KEDRMWZSAEFCSXLMRHRLG9EKLRZJ9NZQEONGFQSKSQMWRZXWVEGHCF9ZKGQITDLGFTZLCQMXVGVYIPKSUPPAOIKVWARJHAEJUZOOJFCFJTGQZWROSCEQSITQWZTQDFKECATBWSIRDFVWH9TXXUVEBRRKOTFSGBMGCMZDK9BJRVEUYOVNSLZNNSTWOB9CFNDJHJENDWSTIMMSWD9HSA9LDGTJDJFRGQCWOHYJNMFFLLDMVQEMCMNVQWMOGCEZUTK9MPULTDMKTIVFNVWXDNTRHNREHEDWIZCCRFVM9TXCTLBLYEKTRVVWHKZDOSYOBNLEKPNXMDZGLSVBBNBJIBZYBXZCENKVIXMNGUJUYFGFYFBKELSBZROQWKMESZTWDEEUCSXOWFSNJUGGJANMJFCBJV9GMDDJCAJHWKYQFJLFWZESTSIRBZQWYHGQDFUUWF9F9HXSBZRKBDLRWXSMKW99KPWJOQYPMHB9KWALSVLFNANYSMNWSJLUOCBKAWAJHNWMLPLXHVEPTKBPWLWHMDZ99999999999999999999999999999999999999999999999999999FDKW9AD99A99999999B99999999RJPQGE9AXNBBLPQ9IFJXFJTXJAIGDDXAWXGAVU9PJQNYFADJDIDBTJKSAEBBX9NWJGZVAVXYWXPCEFFH99XJLIVAZYJGPKEHANESX9DORLEIOIFL9WVQBBNUDRZDGJMVSRTSFKOJPIMEXPBCPGKUUEKRGAXOB99999XIFVJWCABR9XQKOWCXJAZDZWMWWBVHROBOPISAWDIJTSFPVAUZNJUMNUCOFIBIAAVIOWDTCNIPRVA9999999999999999999999999999999RSIDBWNOF999999999K99999999POWSRVIO9VMIJ99FKKNKGPNNNNP", + "COSNPEACSPESTKTUS9CSMOCWVUBLERQJQGLLA9AYRRXMOVOBULQZZZJPLNELEX9EVZDAFOHBB9FBAYOTDB9YGJAVTDWNPJVVGEPFJ9QBYLXAYZEETY9GMYQRIRAIPWMJUFMFRSQRCEIOWISMBJKGCIGIIYNMXAYLXZIBNKANT9DSQXPBUPWGUJTOMD9JKSNFZJJFBZYBWRWQIZFOZXQWNJTRZTUPHVEGTAOLMBEPXOB9TCEBNIWXQUHNQJZHAOWGVOOOTSCTEUEMXJUODZP9CDLCMEXAW9YJGVNREENETEYXOAKZVRJVUSCRUSRCNZUGUOLCNHQITWYPJVJGPVULASTONDZRDVCNGJSN9DMMUORCOIBTITSPBXSAVTOHSAXHMGH99APRTQIVCEI9NWYKDOZCCWBNZGGCUCISSMGJHJ9OVSFBZVFMBODKRKSLIDESGFDQITCYFARWYTLFKSPLNVQ9LTYFODCHBAJKCWVZNBLOJH9ABFGDFOXHDNHZILINMYMVZYWTFJJOLADPTZRPJEMGPFQNYTFDGOKMDTDSKPTHIDWLMRSSACZMTHUCWVZHCYSPJRATABZ9JHWEBGTTQORGFOYDWUIYFYGOGGTYGYYI9RPKPMCTYNYKHHNWQKJTDQDHTRUYQPMCDDLVUUHUBPWGPMNQYPKHDHGIUSXJGNUXIYGZRGVYPTTV9KXJDLGVVXRIWIGKZJVCQ9DUWAYNDRKYCGKZDEOKZQCXELSQXLGLAPBDIWRTKGSIRNAZ9XFEGMWOWLPUA9TSGYRDOU9GKY9JJDKCJKKGJCRARGSNECJ9SEVAGPPJMBTMGNWSLNSFBOEYMIQBKOVSCTDIWCOQTHBVTZOFNNIYKKJLAROKIRFFPCLSOWO9BVNNJSXEYLJASPUEFKCEI9SGWADNGS9MZAKFTCAYNRVKGDIGYDTPGJXWFAEFGG9BX9XCNBZGQULH9IPPIQ9VFVAXGELUMCTE9TOFOGCTFYBYEBFOKKAESIIHEWLYLQKWDHPYNELJKMZHDSDSK9ZWFP99FQFLUXGTSVDMGHIFZFUCYTYPVEFEDM9TOHCQVIBXARYWZEKTWEOVSBYZTJZRUFZJCLRBTOWPTKY99CYQTGPMAKFYALIWPEQLUB9RGANTKDDMSDSJOTNPFZCXKJUGTJWWPK9FAJGPJOEBRHL9ZU9XRBDJBVMFIZIZFGKETH9RJUSDIOWDZIXFOJLT9DSIAWSCEYBNOGOAZQPZKVZTBXGACCBVQGFNGXNQFTVPUGUOHNBKTUERLSTDXGRKZSATIRHDBSXARYATBHOLUHCQGPRMQGSJUAXUCWKDXAUGORHFVVVDZQEYYGSV9IUM9VTMHJCIKBQD9TQLLYTZHAKDHPFGYMRGJOEDJKQTBBNFCKDEPYTPWUJEOPRJWHAILMVW9AXS9HAT9GCGRXZEHWNDOVGKGPWFXFC9IKLVZJCTGZUJFRHGL9FZWZYP9TPOWGOUTQ9INYBVTD9N9OV9AM9GYRJ99MRWRWLOAKJXMDTFNS9NZQAYGVZRJ9WVSIDRRQLGKH9VPUVKFWZMSRVVLKXUGJAZESC9LEYDNNDWS9WTWNFPCR9YKPOMIJAVHCRCJVBDFWSTDN9BPJRDMGZFXWTY9GUX9WEPJQMXSECFPREXAJ9DU9OLMNQFTF99QYZOGDUTMBHMNMCNUQTU9FTNEUNBT9NQO9YADGY9C9LQR9DYVJZE9GMMYAOUSWUIVJ9EAR9EEFCO9SZTQ9JQHPIJGXIOQZHOSUSVMHBOLTYSEONVLRISNAN9KJHHFFISTYIHYVAMXLPNALDELETXVSWGUWOIVKNAKZKNNRVNJKE9WMNQBXFGXSYZQQSIODAKINA9CPBOGAYKJ9V9GS9HERXSVYPEILYXC9GURBREBCXRZJVXHUOPGYFLPIFBCOTMHFTAAUQUZVEKOWYFFGSCXDFDBTSGZRMPQR9EYSZYGQETCWFDRXVOXXCUBOQTBQGVLAONSTSLURFYSDPAVNCXNWVPQOUORMVSLYISQUQKXFYYMJSGHQP9VAAZABEAGJMCPIEBZQSYCPS9CNKFBDRDTDA9C9MVKGUISRX9ODVUJNPDWMZ9ESYXJ99ETDGSTJZMZYJEGRUWTORMCLNHLDQ9YIJWPMDYGGJQHMFPJ9VKINJUESTCBJQXFTCJFNUXPZONVKXJRAYGKIWHXSBZRKBDLRWXSMKW99KPWJOQYPMHB9KWALSVLFNANYSMNWSJLUOCBKAWAJHNWMLPLXHVEPTKBPWLWHMD999999999999999999999999999999999999999999999999999999FDKW9AD99B99999999B99999999RJPQGE9AXNBBLPQ9IFJXFJTXJAIGDDXAWXGAVU9PJQNYFADJDIDBTJKSAEBBX9NWJGZVAVXYWXPCEFFH9XIFVJWCABR9XQKOWCXJAZDZWMWWBVHROBOPISAWDIJTSFPVAUZNJUMNUCOFIBIAAVIOWDTCNIPRVA9999FTXZAEQIKGFJABOGCCQGOMKNASGWCQWMNIMRDTHKPJMTKDGMEQNZOAYARIYEWFZYJALRKFODOITTZ9999999999999999999999999999999LLHDBWNOF999999999K99999999POWSRVIO9VMIJ99UMGMTTTPNNNV", }; + nonTail.store(tangle, snapshot); + List transactions = persistAndMapTxs(trytes); + List bundleTxs = bundleValidator.validate(tangle, true, snapshot, + transactions.get(0).getHash()); + assertTrue("no txs from the invalid bundle should be returned", bundleTxs.isEmpty()); + } + @Test public void validateValidBundle() throws Exception { String[] trytes = { @@ -87,7 +172,7 @@ public void validateValidBundle() throws Exception { "COSNPEACSPESTKTUS9CSMOCWVUBLERQJQGLLA9AYRRXMOVOBULQZZZJPLNELEX9EVZDAFOHBB9FBAYOTDB9YGJAVTDWNPJVVGEPFJ9QBYLXAYZEETY9GMYQRIRAIPWMJUFMFRSQRCEIOWISMBJKGCIGIIYNMXAYLXZIBNKANT9DSQXPBUPWGUJTOMD9JKSNFZJJFBZYBWRWQIZFOZXQWNJTRZTUPHVEGTAOLMBEPXOB9TCEBNIWXQUHNQJZHAOWGVOOOTSCTEUEMXJUODZP9CDLCMEXAW9YJGVNREENETEYXOAKZVRJVUSCRUSRCNZUGUOLCNHQITWYPJVJGPVULASTONDZRDVCNGJSN9DMMUORCOIBTITSPBXSAVTOHSAXHMGH99APRTQIVCEI9NWYKDOZCCWBNZGGCUCISSMGJHJ9OVSFBZVFMBODKRKSLIDESGFDQITCYFARWYTLFKSPLNVQ9LTYFODCHBAJKCWVZNBLOJH9ABFGDFOXHDNHZILINMYMVZYWTFJJOLADPTZRPJEMGPFQNYTFDGOKMDTDSKPTHIDWLMRSSACZMTHUCWVZHCYSPJRATABZ9JHWEBGTTQORGFOYDWUIYFYGOGGTYGYYI9RPKPMCTYNYKHHNWQKJTDQDHTRUYQPMCDDLVUUHUBPWGPMNQYPKHDHGIUSXJGNUXIYGZRGVYPTTV9KXJDLGVVXRIWIGKZJVCQ9DUWAYNDRKYCGKZDEOKZQCXELSQXLGLAPBDIWRTKGSIRNAZ9XFEGMWOWLPUA9TSGYRDOU9GKY9JJDKCJKKGJCRARGSNECJ9SEVAGPPJMBTMGNWSLNSFBOEYMIQBKOVSCTDIWCOQTHBVTZOFNNIYKKJLAROKIRFFPCLSOWO9BVNNJSXEYLJASPUEFKCEI9SGWADNGS9MZAKFTCAYNRVKGDIGYDTPGJXWFAEFGG9BX9XCNBZGQULH9IPPIQ9VFVAXGELUMCTE9TOFOGCTFYBYEBFOKKAESIIHEWLYLQKWDHPYNELJKMZHDSDSK9ZWFP99FQFLUXGTSVDMGHIFZFUCYTYPVEFEDM9TOHCQVIBXARYWZEKTWEOVSBYZTJZRUFZJCLRBTOWPTKY99CYQTGPMAKFYALIWPEQLUB9RGANTKDDMSDSJOTNPFZCXKJUGTJWWPK9FAJGPJOEBRHL9ZU9XRBDJBVMFIZIZFGKETH9RJUSDIOWDZIXFOJLT9DSIAWSCEYBNOGOAZQPZKVZTBXGACCBVQGFNGXNQFTVPUGUOHNBKTUERLSTDXGRKZSATIRHDBSXARYATBHOLUHCQGPRMQGSJUAXUCWKDXAUGORHFVVVDZQEYYGSV9IUM9VTMHJCIKBQD9TQLLYTZHAKDHPFGYMRGJOEDJKQTBBNFCKDEPYTPWUJEOPRJWHAILMVW9AXS9HAT9GCGRXZEHWNDOVGKGPWFXFC9IKLVZJCTGZUJFRHGL9FZWZYP9TPOWGOUTQ9INYBVTD9N9OV9AM9GYRJ99MRWRWLOAKJXMDTFNS9NZQAYGVZRJ9WVSIDRRQLGKH9VPUVKFWZMSRVVLKXUGJAZESC9LEYDNNDWS9WTWNFPCR9YKPOMIJAVHCRCJVBDFWSTDN9BPJRDMGZFXWTY9GUX9WEPJQMXSECFPREXAJ9DU9OLMNQFTF99QYZOGDUTMBHMNMCNUQTU9FTNEUNBT9NQO9YADGY9C9LQR9DYVJZE9GMMYAOUSWUIVJ9EAR9EEFCO9SZTQ9JQHPIJGXIOQZHOSUSVMHBOLTYSEONVLRISNAN9KJHHFFISTYIHYVAMXLPNALDELETXVSWGUWOIVKNAKZKNNRVNJKE9WMNQBXFGXSYZQQSIODAKINA9CPBOGAYKJ9V9GS9HERXSVYPEILYXC9GURBREBCXRZJVXHUOPGYFLPIFBCOTMHFTAAUQUZVEKOWYFFGSCXDFDBTSGZRMPQR9EYSZYGQETCWFDRXVOXXCUBOQTBQGVLAONSTSLURFYSDPAVNCXNWVPQOUORMVSLYISQUQKXFYYMJSGHQP9VAAZABEAGJMCPIEBZQSYCPS9CNKFBDRDTDA9C9MVKGUISRX9ODVUJNPDWMZ9ESYXJ99ETDGSTJZMZYJEGRUWTORMCLNHLDQ9YIJWPMDYGGJQHMFPJ9VKINJUESTCBJQXFTCJFNUXPZONVKXJRAYGKIWHXSBZRKBDLRWXSMKW99KPWJOQYPMHB9KWALSVLFNANYSMNWSJLUOCBKAWAJHNWMLPLXHVEPTKBPWLWHMD999999999999999999999999999999999999999999999999999999FDKW9AD99B99999999B99999999RJPQGE9AXNBBLPQ9IFJXFJTXJAIGDDXAWXGAVU9PJQNYFADJDIDBTJKSAEBBX9NWJGZVAVXYWXPCEFFH9XIFVJWCABR9XQKOWCXJAZDZWMWWBVHROBOPISAWDIJTSFPVAUZNJUMNUCOFIBIAAVIOWDTCNIPRVA9999FTXZAEQIKGFJABOGCCQGOMKNASGWCQWMNIMRDTHKPJMTKDGMEQNZOAYARIYEWFZYJALRKFODOITTZ9999999999999999999999999999999LLHDBWNOF999999999K99999999POWSRVIO9VMIJ99UMGMTTTPNNNV", }; List transactions = persistAndMapTxs(trytes); - List bundleTxs = bundleValidator.validate(tangle, snapshot, + List bundleTxs = bundleValidator.validate(tangle, true, snapshot, transactions.get(0).getHash()); assertFalse("all txs from the valid bundle should be returned", bundleTxs.isEmpty()); assertEquals("should have changed the validity of the tail tx to valid", 1, @@ -108,7 +193,7 @@ public void validateBiggerValidBundle() throws Exception { "999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999G9EQ9IARCEMSKSMKYWKHUCGSXMGCT9LAAIHBWIWNIYWFTSGJRHSLXGXQNXKHJOXNMWQHPYMKOKSRL9TDDOJCQ9QNBOC999999999999999999999999999999999999999999999EULM9D99H99999999H99999999BJVQHAHPDFGNEELCFLIEXWWKZXLPUZQMDXFGKKOGPDEGJDALESJZFVKIIRJSGKXDNWJOQBRIUEBTGACG9ABQONKFWTKFCSKMUPVGPRNYIZTWETHIUNG9MFYKXLZRJHDUVIDBAL9QVDU9B9DHTYPMMDBCEFTLRA9999RNEQYL9XYIWHGTEQHZAPXZPWRRXOGXPJXHCV9OSTYLL9AVTMEXKG9HPIPKITPXQGEWIJIXVIEUNS99999999999999999999999999999999GBMYRLVNF999999999MMMMMMMMMAYSMPUWFG9OTFD9YMETBNVGMLRA", }; List transactions = persistAndMapTxs(trytes); - List bundleTxs = bundleValidator.validate(tangle, snapshot, + List bundleTxs = bundleValidator.validate(tangle, true, snapshot, transactions.get(0).getHash()); assertFalse("all txs from the valid bundle should be returned", bundleTxs.isEmpty()); assertEquals("should have changed the validity of the tail tx to valid", 1, @@ -125,7 +210,7 @@ public void validateBundleWithInvalidSignature() throws Exception { "999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999MUN9LXIQBWCSJDSWFARRJMIQGJWZMJQ9AERHUZGLTH9OAMCJBIJHPAOQIECPTNYHUWZLCSIYNKMNFHSWCNSA999999999999999999999999999999999999999999999999999VKUIAAD99C99999999C99999999DIODRCKI9IUWGBTDTILEUVUPLHEMQZAYKWIULYHGPKVYSGCI9ANDOOBQTXAXIHGSVOLLXOKTRCHZFDHIZNRBM9KWMOYBKSGKVDTVOXQSDJUTIIPVIAOMOYWFXLOFVP9OP9TBCDTJXZNYZQBALCPJCDKWKQQARZ9999FJHO9TPBCDABULPM9ERDYUZMQMNRPEFSYBDYWILOXQFRGAASGBQBBZMGICNJEPTFAC9ENSINGMURA9999999999999999999999999999999LKQRCWPOF999999999K99999999ID99999999RUTA9999999999999", }; List transactions = persistAndMapTxs(trytes); - List bundleTxs = bundleValidator.validate(tangle, snapshot, + List bundleTxs = bundleValidator.validate(tangle, true, snapshot, transactions.get(0).getHash()); assertTrue("no transactions should be returned", bundleTxs.isEmpty()); assertEquals("should have changed the validity of the tail tx to invalid", -1, @@ -139,7 +224,7 @@ public void validateBundleWithMissingNonTailTransaction() throws Exception { "YU9WINMHSBIJ9HRBKDEHZYOCDGOZ9HZSSVXAYYOD9AMAFBCROMLSFDFIAQKEXAVFDLJYVABRSONSTTXLCQJTSDRFDEXZIORLRSQDZQODNCHAAFUPGBCEVYNRSJQGUZRAQIIEZPFQGWPQIBMDVNZGQAKCU9VLHGYSNDPHCMOCTRYQOYHFPRQSGHSDEF9MSHGXYGUTHSBSF9SOSJQFOZXMQGCUXAYWLBJHXOHTOM9ITZPISEXLAYAZBHTZDDCOIHKDBL9AAXZEDFHDYCPXLDGR9JTVXDXBC9DRDVREEEVGVPWVEZHJBYQYTGDEWQHVEDTAVLWW9WBXOIGDGUAAAVKFMPVKSYBVOIWPYGCMACEAAHGUIQVFLFMSCPKTYQWJDGMDNLMDIUUYI9SZOFQPLKKACESSWIXPFYZWDHETYUGEVASPHBGQPJPWVESSUUEVUYDYNQUQURSMCEPKIHYXNFHZSHIIDTMEZQ9IPSHTBDK9UHRSIIEJRILBCMVUMGAGBMHKEMUUKQDCYAVIAN9MBWJCAUSSJRSAUEUBGEFFCFSOBVGLXBAIDOH9NXWQUSIFUINEVZUWELLVIOSSQRVHMTDBCATBWFSDJJDLURAUKEQEIRICSCXAOJNPWHQKYVTRLCRKHLFHHHPYSMUEAPMQPMPS9CHCIYXEYRNIOOGRWKVHUGIWJZIGPWGZGBGYTVC9UVEJ9JBIYMDHYXLWIDXHZVFCCPLYXDPQPJGMKNZMMONGNXSNNPCGEGRJTMCPTJONGPAEDMSHMGAPGBDWVCSCTJAECSHTEGBMVCDZMQYZ9IHERWGCKAIMFOJRAEDRDSWJASV9XYS9CTOEETPQHRKJZ9FPWTVCGLEJQJXVVDTYZ9DNSHQAINKROQQIPTFGTABEELYPBQHGNNB9NCL9BVTG9MH99BPIRNNGC9MQQRH9YUDYSUJIDSZKCIGEKFSJ9PXUYKVPUZYFIREUAMUZE9EIMJK9YGAZXZBPRXIRDCEVDHWOCFIJTVRCRZRRJGFLILKVNMJAX9MUMTXCWTUTP9NXBZUMAYYY9DNVZTCROBCMMHVKSQRXMGROCMMCDENWQWBV9LEIJONWTQXOUQZOTGEJHJELYZOPICLDWPHVCMBFSYHPIJYZQDEGYITVPYNQMXPRTPKADLUKW9AEMSMGIWCUMOQMUDN9HZGYKTNVVNLOQAFVETEDJMJVKYCE9RE9AJP9RMYFKGDEC9GIGAFXEXIYXPFBOMRPULVJMYTQEPDMMIABDHJGRNNCKADZAUFV9WCWFYFHXLCED9E9SKKYHVKDTGNCHEKFJL9YJHTSRUVKEPHPCQSFNBXKOJNGBSEOZLVVGNUQTRACVGEKDSORUHVSDSQNUZASWXRADGAUDYDPODDEDNDDT9OAEARSBLPVSYAOEYYZJIZIZDENQOKMXATCTD9NEFVQWNCET9N9IXWCCWHYUBDTDJUZFXWMATRYSGJUQAFKAPIQIIXVFFEFNLGDPAIGKONUWLUTQTKZJYDEGPASTOMM9QTGOMYHFUKMNEVWPCMTLPDSOEEWDBRHZPZJIFMBUTY9QPRLPSVURIRMCWLH9YIZBOTJQMMF9ZP9VCCEGWVKSSQNANP9ZXVCAFGAVBXDJHUMKTKUBZRNKUGJTVUPDZMUK9KZI9EKY9DQDUNA9ZVPCROKYYPISSTDOJZJXMXZWSGZBEPKAUYPBPUTDVQKUYXWYTZXLYTCLLKLJUUJGFFCMZFKDTJET9KEDRMWZSAEFCSXLMRHRLG9EKLRZJ9NZQEONGFQSKSQMWRZXWVEGHCF9ZKGQITDLGFTZLCQMXVGVYIPKSUPPAOIKVWARJHAEJUZOOJFCFJTGQZWROSCEQSITQWZTQDFKECATBWSIRDFVWH9TXXUVEBRRKOTFSGBMGCMZDK9BJRVEUYOVNSLZNNSTWOB9CFNDJHJENDWSTIMMSWD9HSA9LDGTJDJFRGQCWOHYJNMFFLLDMVQEMCMNVQWMOGCEZUTK9MPULTDMKTIVFNVWXDNTRHNREHEDWIZCCRFVM9TXCTLBLYEKTRVVWHKZDOSYOBNLEKPNXMDZGLSVBBNBJIBZYBXZCENKVIXMNGUJUYFGFYFBKELSBZROQWKMESZTWDEEUCSXOWFSNJUGGJANMJFCBJV9GMDDJCAJHWKYQFJLFWZESTSIRBZQWYHGQDFUUWF9F9HXSBZRKBDLRWXSMKW99KPWJOQYPMHB9KWALSVLFNANYSMNWSJLUOCBKAWAJHNWMLPLXHVEPTKBPWLWHMDZ99999999999999999999999999999999999999999999999999999FDKW9AD99A99999999B99999999RJPQGE9AXNBBLPQ9IFJXFJTXJAIGDDXAWXGAVU9PJQNYFADJDIDBTJKSAEBBX9NWJGZVAVXYWXPCEFFH99XJLIVAZYJGPKEHANESX9DORLEIOIFL9WVQBBNUDRZDGJMVSRTSFKOJPIMEXPBCPGKUUEKRGAXOB99999XIFVJWCABR9XQKOWCXJAZDZWMWWBVHROBOPISAWDIJTSFPVAUZNJUMNUCOFIBIAAVIOWDTCNIPRVA9999999999999999999999999999999RSIDBWNOF999999999K99999999POWSRVIO9VMIJ99FKKNKGPNNNNP", }; List transactions = persistAndMapTxs(trytes); - List bundleTxs = bundleValidator.validate(tangle, snapshot, + List bundleTxs = bundleValidator.validate(tangle, true, snapshot, transactions.get(0).getHash()); assertTrue("no transactions should be returned", bundleTxs.isEmpty()); assertEquals("should not have changed the validity of the tail tx since the bundle is incomplete", 0, @@ -153,7 +238,7 @@ public void validateBundleWithMissingTailTransaction() throws Exception { "COSNPEACSPESTKTUS9CSMOCWVUBLERQJQGLLA9AYRRXMOVOBULQZZZJPLNELEX9EVZDAFOHBB9FBAYOTDB9YGJAVTDWNPJVVGEPFJ9QBYLXAYZEETY9GMYQRIRAIPWMJUFMFRSQRCEIOWISMBJKGCIGIIYNMXAYLXZIBNKANT9DSQXPBUPWGUJTOMD9JKSNFZJJFBZYBWRWQIZFOZXQWNJTRZTUPHVEGTAOLMBEPXOB9TCEBNIWXQUHNQJZHAOWGVOOOTSCTEUEMXJUODZP9CDLCMEXAW9YJGVNREENETEYXOAKZVRJVUSCRUSRCNZUGUOLCNHQITWYPJVJGPVULASTONDZRDVCNGJSN9DMMUORCOIBTITSPBXSAVTOHSAXHMGH99APRTQIVCEI9NWYKDOZCCWBNZGGCUCISSMGJHJ9OVSFBZVFMBODKRKSLIDESGFDQITCYFARWYTLFKSPLNVQ9LTYFODCHBAJKCWVZNBLOJH9ABFGDFOXHDNHZILINMYMVZYWTFJJOLADPTZRPJEMGPFQNYTFDGOKMDTDSKPTHIDWLMRSSACZMTHUCWVZHCYSPJRATABZ9JHWEBGTTQORGFOYDWUIYFYGOGGTYGYYI9RPKPMCTYNYKHHNWQKJTDQDHTRUYQPMCDDLVUUHUBPWGPMNQYPKHDHGIUSXJGNUXIYGZRGVYPTTV9KXJDLGVVXRIWIGKZJVCQ9DUWAYNDRKYCGKZDEOKZQCXELSQXLGLAPBDIWRTKGSIRNAZ9XFEGMWOWLPUA9TSGYRDOU9GKY9JJDKCJKKGJCRARGSNECJ9SEVAGPPJMBTMGNWSLNSFBOEYMIQBKOVSCTDIWCOQTHBVTZOFNNIYKKJLAROKIRFFPCLSOWO9BVNNJSXEYLJASPUEFKCEI9SGWADNGS9MZAKFTCAYNRVKGDIGYDTPGJXWFAEFGG9BX9XCNBZGQULH9IPPIQ9VFVAXGELUMCTE9TOFOGCTFYBYEBFOKKAESIIHEWLYLQKWDHPYNELJKMZHDSDSK9ZWFP99FQFLUXGTSVDMGHIFZFUCYTYPVEFEDM9TOHCQVIBXARYWZEKTWEOVSBYZTJZRUFZJCLRBTOWPTKY99CYQTGPMAKFYALIWPEQLUB9RGANTKDDMSDSJOTNPFZCXKJUGTJWWPK9FAJGPJOEBRHL9ZU9XRBDJBVMFIZIZFGKETH9RJUSDIOWDZIXFOJLT9DSIAWSCEYBNOGOAZQPZKVZTBXGACCBVQGFNGXNQFTVPUGUOHNBKTUERLSTDXGRKZSATIRHDBSXARYATBHOLUHCQGPRMQGSJUAXUCWKDXAUGORHFVVVDZQEYYGSV9IUM9VTMHJCIKBQD9TQLLYTZHAKDHPFGYMRGJOEDJKQTBBNFCKDEPYTPWUJEOPRJWHAILMVW9AXS9HAT9GCGRXZEHWNDOVGKGPWFXFC9IKLVZJCTGZUJFRHGL9FZWZYP9TPOWGOUTQ9INYBVTD9N9OV9AM9GYRJ99MRWRWLOAKJXMDTFNS9NZQAYGVZRJ9WVSIDRRQLGKH9VPUVKFWZMSRVVLKXUGJAZESC9LEYDNNDWS9WTWNFPCR9YKPOMIJAVHCRCJVBDFWSTDN9BPJRDMGZFXWTY9GUX9WEPJQMXSECFPREXAJ9DU9OLMNQFTF99QYZOGDUTMBHMNMCNUQTU9FTNEUNBT9NQO9YADGY9C9LQR9DYVJZE9GMMYAOUSWUIVJ9EAR9EEFCO9SZTQ9JQHPIJGXIOQZHOSUSVMHBOLTYSEONVLRISNAN9KJHHFFISTYIHYVAMXLPNALDELETXVSWGUWOIVKNAKZKNNRVNJKE9WMNQBXFGXSYZQQSIODAKINA9CPBOGAYKJ9V9GS9HERXSVYPEILYXC9GURBREBCXRZJVXHUOPGYFLPIFBCOTMHFTAAUQUZVEKOWYFFGSCXDFDBTSGZRMPQR9EYSZYGQETCWFDRXVOXXCUBOQTBQGVLAONSTSLURFYSDPAVNCXNWVPQOUORMVSLYISQUQKXFYYMJSGHQP9VAAZABEAGJMCPIEBZQSYCPS9CNKFBDRDTDA9C9MVKGUISRX9ODVUJNPDWMZ9ESYXJ99ETDGSTJZMZYJEGRUWTORMCLNHLDQ9YIJWPMDYGGJQHMFPJ9VKINJUESTCBJQXFTCJFNUXPZONVKXJRAYGKIWHXSBZRKBDLRWXSMKW99KPWJOQYPMHB9KWALSVLFNANYSMNWSJLUOCBKAWAJHNWMLPLXHVEPTKBPWLWHMD999999999999999999999999999999999999999999999999999999FDKW9AD99B99999999B99999999RJPQGE9AXNBBLPQ9IFJXFJTXJAIGDDXAWXGAVU9PJQNYFADJDIDBTJKSAEBBX9NWJGZVAVXYWXPCEFFH9XIFVJWCABR9XQKOWCXJAZDZWMWWBVHROBOPISAWDIJTSFPVAUZNJUMNUCOFIBIAAVIOWDTCNIPRVA9999FTXZAEQIKGFJABOGCCQGOMKNASGWCQWMNIMRDTHKPJMTKDGMEQNZOAYARIYEWFZYJALRKFODOITTZ9999999999999999999999999999999LLHDBWNOF999999999K99999999POWSRVIO9VMIJ99UMGMTTTPNNNV", }; List transactions = persistAndMapTxs(trytes); - List bundleTxs = bundleValidator.validate(tangle, snapshot, + List bundleTxs = bundleValidator.validate(tangle, true, snapshot, transactions.get(0).getHash()); assertTrue("no transactions should be returned", bundleTxs.isEmpty()); // special case, since there's no tail, we don't actually update the validity in the database @@ -171,7 +256,8 @@ public void validateBundleWithInvalidIndices() throws Exception { "999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999NVPJQUUVRZYJATRWDEXX9UDHZGBPHPMDXYGQSZEYMZA9NOVVNLN9HCDKXRXCWXGCXSZVQVCMPEZKJFJZWNSA999999999999999999999999999999999999999999999999999TXVIAAD99C99999999C99999999YD9SJSKIJDSDTUQDYJOAHDEJBFKXNETCGPFQBF9TGKCTGJINK9OWSATFAFLJWOGWFHZRPHOJVFCAYGPJCWBRFOY9ULDPMHFZYSUKD9OTYKWJUKCXPOIZVACZNPPNTVFPNYUZNQJIYOMRMSULQPVWFLLIVODQDZ9999RM9DMI9FIPZMUBPLGYMXPLQGIXLMUGRQNECIYTVDFTR9TYFJTULEHVECMALIZMJVN9UKZNSKORO999999999999999999999999999999999SOLHCWPOF999999999K99999999SGC9999999FVC99999999999999", }; List transactions = persistAndMapTxs(trytes); - List bundleTxs = bundleValidator.validate(tangle, snapshot, + List bundleTxs = bundleValidator.validate(tangle, true, + snapshot, transactions.get(0).getHash()); assertTrue("no transactions should be returned", bundleTxs.isEmpty()); assertEquals("should have changed the validity of the tail tx to invalid", -1, @@ -188,7 +274,7 @@ public void validateBundleWithInvalidBundleHashOnTailTransaction() throws Except "999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999YIDFNIWFHPVPDKBAFQULFV9DFAOCXEPX9JZDIOWVHEUHOQORBPKOONGSYUJVKTYFJZBZCWKTHMWTCY99WNSA999999999999999999999999999999999999999999999999999EUWIAAD99C99999999C99999999EJJWPHCLV9KKDOBRKCBKWP9SQSOQYCLSHBLWNL9BEPOLGDXNISVPZGYSZRQXBHJKAIAZAFJS9K9TGJWTWMVTOHDRMCONUSSESCTMMIFDNGCLFFVBTUWV9AWUI9NSSLVZFKFXBHJMETHLXWGMGVXOKABSVOSFFZ9999MVTOHDRMCONUSSESCTMMIFDNGCLFFVBTUWV9AWUI9NSSLVZFKFXBHJMETHLXWGMGVXOKABSVOSFFZ9999999999999999999999999999999DSYOEWPOF999999999K99999999M999999999IJJC9999999999999", }; List transactions = persistAndMapTxs(trytes); - List bundleTxs = bundleValidator.validate(tangle, snapshot, + List bundleTxs = bundleValidator.validate(tangle, true, snapshot, transactions.get(0).getHash()); assertTrue("no transactions should be returned", bundleTxs.isEmpty()); assertEquals("should not have changed the validity of the tail tx since the bundle is incomplete " + @@ -207,7 +293,7 @@ public void validateBundleWithInvalidBundleHashOnNonTailTransaction() throws Exc "999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999SV9SCHGZVNZDJCFXFFJHENC9KONVG9JNXMIHLVFKNRFWUFFJRT9ADFIMHAXZTYAITABMURFFNWCGHIDUYNSA999999999999999999999999999999999999999999999999999DDWIAAD99C99999999C99999999MLKHIDVEN9KBPWKTGUY9QOQDRLJVSBAGXOBFHWIQRCQYIHVQIQJQJAUNWGHQBINDBAXBDBBJAZEXXGFEWUASDRXHKLY9PRNTZSNOZLLJTMITWQQCJAQLQOPWQIZXXHOPBGLLMIWMETVXAXDLSAWTRQDBDVDTQ99999NTNMNQLYLR9MNUDQITVBEZTWPMFWDGMQNOAJYMSFLMZKV9HBAQGTFPXDGNZUXOCNDDBYEHHUTHOD99999999999999999999999999999999OXQBEWPOF999999999K99999999KKA9999999LJJ99999999999999", }; List transactions = persistAndMapTxs(trytes); - List bundleTxs = bundleValidator.validate(tangle, snapshot, + List bundleTxs = bundleValidator.validate(tangle, true, snapshot, transactions.get(0).getHash()); assertTrue("no transactions should be returned", bundleTxs.isEmpty()); assertEquals("should not have changed the validity of the tail tx since the bundle is incomplete " + @@ -230,7 +316,7 @@ public void validateSemanticsOfValidBundle() { bundleTxsMapping.put(tvm.getHash(), tvm); } List bundleTxs = new LinkedList<>(); - BundleValidator.Validity validity = BundleValidator.validateBundleSemantics(transactions.get(0), + BundleValidator.Validity validity = bundleValidator.validateBundleSemantics(transactions.get(0), bundleTxsMapping, bundleTxs); assertEquals("the bundle semantics should be valid", BundleValidator.Validity.VALID, validity); assertEquals("built up bundle txs list should be the same", transactions, bundleTxs); @@ -248,7 +334,8 @@ public void validateSemanticsOfBundleWithMissingTransaction() { bundleTxsMapping.put(tvm.getHash(), tvm); } List bundleTxs = new LinkedList<>(); - BundleValidator.Validity validity = BundleValidator.validateBundleSemantics(transactions.get(0), bundleTxsMapping, + BundleValidator.Validity validity = bundleValidator.validateBundleSemantics(transactions.get(0), + bundleTxsMapping, bundleTxs); assertEquals("the bundle semantic's validity should be unknown since were are missing a tx", BundleValidator.Validity.UNKNOWN, validity); @@ -269,7 +356,8 @@ public void validateSemanticsOfBundleWithInvalidIndices() { bundleTxsMapping.put(tvm.getHash(), tvm); } List bundleTxs = new LinkedList<>(); - BundleValidator.Validity validity = BundleValidator.validateBundleSemantics(transactions.get(0), bundleTxsMapping, bundleTxs); + BundleValidator.Validity validity = bundleValidator.validateBundleSemantics(transactions.get(0), + bundleTxsMapping, bundleTxs); assertEquals("the bundle semantics should be invalid", BundleValidator.Validity.INVALID, validity); } @@ -290,7 +378,8 @@ public void validateSemanticsOfBundleWhichSpendsMoreThanMaxSupply() { bundleTxsMapping.put(tvm.getHash(), tvm); } List bundleTxs = new LinkedList<>(); - BundleValidator.Validity validity = BundleValidator.validateBundleSemantics(transactions.get(0), bundleTxsMapping, bundleTxs); + BundleValidator.Validity validity = bundleValidator.validateBundleSemantics(transactions.get(0), + bundleTxsMapping, bundleTxs); assertEquals("the bundle semantics should be invalid", BundleValidator.Validity.INVALID, validity); } @@ -360,7 +449,7 @@ public void validationModeAll() { }; List transactions = persistAndMapTxs(trytes); try { - BundleValidator.Validity validity = BundleValidator.validate(tangle, transactions.get(0).getHash(), + BundleValidator.Validity validity = bundleValidator.validate(tangle, transactions.get(0).getHash(), BundleValidator.MODE_VALIDATE_ALL, new LinkedList<>()); assertEquals("the bundle should be valid", BundleValidator.Validity.VALID, validity); } catch (Exception e) { @@ -458,7 +547,7 @@ public void validationModeAllWithInvalidBundle() { }; List transactions = persistAndMapTxs(trytes); try { - BundleValidator.Validity validity = BundleValidator.validate(tangle, transactions.get(0).getHash(), + BundleValidator.Validity validity = bundleValidator.validate(tangle, transactions.get(0).getHash(), BundleValidator.MODE_VALIDATE_ALL, new LinkedList<>()); assertEquals("the bundle should be invalid", BundleValidator.Validity.INVALID, validity); } catch (Exception e) { @@ -477,7 +566,8 @@ public void validationModeBundleHash() { }; List transactions = persistAndMapTxs(trytes); try { - BundleValidator.Validity validity = BundleValidator.validate(tangle, transactions.get(0).getHash(), + BundleValidator.Validity validity = bundleValidator.validate( + tangle, transactions.get(0).getHash(), BundleValidator.MODE_VALIDATE_BUNDLE_HASH | BundleValidator.MODE_SKIP_TAIL_TX_EXISTENCE | BundleValidator.MODE_SKIP_CACHED_VALIDITY, new LinkedList<>()); @@ -498,7 +588,8 @@ public void validationModeSignaturesOnBundleWithInvalidSemantics() { }; List transactions = persistAndMapTxs(trytes); try { - BundleValidator.Validity validity = BundleValidator.validate(tangle, transactions.get(0).getHash(), + BundleValidator.Validity validity = bundleValidator.validate( + tangle, transactions.get(0).getHash(), BundleValidator.MODE_VALIDATE_SIGNATURES | BundleValidator.MODE_SKIP_TAIL_TX_EXISTENCE | BundleValidator.MODE_SKIP_CACHED_VALIDITY, new LinkedList<>()); @@ -521,7 +612,8 @@ public void validationModeSignaturesOnBundleWithEmptyBundleHash() { }; List transactions = persistAndMapTxs(trytes); try { - BundleValidator.Validity validity = BundleValidator.validate(tangle, transactions.get(0).getHash(), + BundleValidator.Validity validity = bundleValidator.validate( + tangle, transactions.get(0).getHash(), BundleValidator.MODE_VALIDATE_SIGNATURES | BundleValidator.MODE_SKIP_TAIL_TX_EXISTENCE | BundleValidator.MODE_SKIP_CACHED_VALIDITY, new LinkedList<>()); @@ -542,7 +634,8 @@ public void validationModeSignaturesOnBundleWithInvalidBundleHashAndSemantics() }; List transactions = persistAndMapTxs(trytes); try { - BundleValidator.Validity validity = BundleValidator.validate(tangle, transactions.get(0).getHash(), + BundleValidator.Validity validity = bundleValidator.validate( + tangle, transactions.get(0).getHash(), BundleValidator.MODE_VALIDATE_SIGNATURES | BundleValidator.MODE_SKIP_TAIL_TX_EXISTENCE | BundleValidator.MODE_SKIP_CACHED_VALIDITY, new LinkedList<>()); @@ -563,7 +656,8 @@ public void validationModeSignaturesWithInvalidSignatures() { }; List transactions = persistAndMapTxs(trytes); try { - BundleValidator.Validity validity = BundleValidator.validate(tangle, transactions.get(0).getHash(), + BundleValidator.Validity validity = bundleValidator.validate( + tangle, transactions.get(0).getHash(), BundleValidator.MODE_VALIDATE_SIGNATURES | BundleValidator.MODE_SKIP_TAIL_TX_EXISTENCE | BundleValidator.MODE_SKIP_CACHED_VALIDITY, new LinkedList<>()); @@ -572,4 +666,24 @@ public void validationModeSignaturesWithInvalidSignatures() { e.printStackTrace(); } } + + @Test + public void verifyValidateSkippingChecks() { + // valid bundle trytes + String[] trytes = { + "999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999GNXKWQPHMFCEZZCKUCCD9PROQVPCTBVVAPHCLVOPYYQLQRMPGTQXBVZLKGGT9NXHUAASFIAVEOZTBSM9DA99999999999999999999999999RENGLE9BEAT9999999999999999FDKW9AD99999999999B99999999RJPQGE9AXNBBLPQ9IFJXFJTXJAIGDDXAWXGAVU9PJQNYFADJDIDBTJKSAEBBX9NWJGZVAVXYWXPCEFFH9DKZYSMJAQRLXZHCNPTJPJLOONNDZHGNTBIINGYKZQAAWF9LWQTHUQSAXMLQN9DKCQWHIJXNGLGQVA9999XIFVJWCABR9XQKOWCXJAZDZWMWWBVHROBOPISAWDIJTSFPVAUZNJUMNUCOFIBIAAVIOWDTCNIPRVA9999TANGLE9BEAT9999999999999999SVIDBWNOF999999999K99999999POWSRVIO9VMIJ99UGNETMNPNNNE", + "YU9WINMHSBIJ9HRBKDEHZYOCDGOZ9HZSSVXAYYOD9AMAFBCROMLSFDFIAQKEXAVFDLJYVABRSONSTTXLCQJTSDRFDEXZIORLRSQDZQODNCHAAFUPGBCEVYNRSJQGUZRAQIIEZPFQGWPQIBMDVNZGQAKCU9VLHGYSNDPHCMOCTRYQOYHFPRQSGHSDEF9MSHGXYGUTHSBSF9SOSJQFOZXMQGCUXAYWLBJHXOHTOM9ITZPISEXLAYAZBHTZDDCOIHKDBL9AAXZEDFHDYCPXLDGR9JTVXDXBC9DRDVREEEVGVPWVEZHJBYQYTGDEWQHVEDTAVLWW9WBXOIGDGUAAAVKFMPVKSYBVOIWPYGCMACEAAHGUIQVFLFMSCPKTYQWJDGMDNLMDIUUYI9SZOFQPLKKACESSWIXPFYZWDHETYUGEVASPHBGQPJPWVESSUUEVUYDYNQUQURSMCEPKIHYXNFHZSHIIDTMEZQ9IPSHTBDK9UHRSIIEJRILBCMVUMGAGBMHKEMUUKQDCYAVIAN9MBWJCAUSSJRSAUEUBGEFFCFSOBVGLXBAIDOH9NXWQUSIFUINEVZUWELLVIOSSQRVHMTDBCATBWFSDJJDLURAUKEQEIRICSCXAOJNPWHQKYVTRLCRKHLFHHHPYSMUEAPMQPMPS9CHCIYXEYRNIOOGRWKVHUGIWJZIGPWGZGBGYTVC9UVEJ9JBIYMDHYXLWIDXHZVFCCPLYXDPQPJGMKNZMMONGNXSNNPCGEGRJTMCPTJONGPAEDMSHMGAPGBDWVCSCTJAECSHTEGBMVCDZMQYZ9IHERWGCKAIMFOJRAEDRDSWJASV9XYS9CTOEETPQHRKJZ9FPWTVCGLEJQJXVVDTYZ9DNSHQAINKROQQIPTFGTABEELYPBQHGNNB9NCL9BVTG9MH99BPIRNNGC9MQQRH9YUDYSUJIDSZKCIGEKFSJ9PXUYKVPUZYFIREUAMUZE9EIMJK9YGAZXZBPRXIRDCEVDHWOCFIJTVRCRZRRJGFLILKVNMJAX9MUMTXCWTUTP9NXBZUMAYYY9DNVZTCROBCMMHVKSQRXMGROCMMCDENWQWBV9LEIJONWTQXOUQZOTGEJHJELYZOPICLDWPHVCMBFSYHPIJYZQDEGYITVPYNQMXPRTPKADLUKW9AEMSMGIWCUMOQMUDN9HZGYKTNVVNLOQAFVETEDJMJVKYCE9RE9AJP9RMYFKGDEC9GIGAFXEXIYXPFBOMRPULVJMYTQEPDMMIABDHJGRNNCKADZAUFV9WCWFYFHXLCED9E9SKKYHVKDTGNCHEKFJL9YJHTSRUVKEPHPCQSFNBXKOJNGBSEOZLVVGNUQTRACVGEKDSORUHVSDSQNUZASWXRADGAUDYDPODDEDNDDT9OAEARSBLPVSYAOEYYZJIZIZDENQOKMXATCTD9NEFVQWNCET9N9IXWCCWHYUBDTDJUZFXWMATRYSGJUQAFKAPIQIIXVFFEFNLGDPAIGKONUWLUTQTKZJYDEGPASTOMM9QTGOMYHFUKMNEVWPCMTLPDSOEEWDBRHZPZJIFMBUTY9QPRLPSVURIRMCWLH9YIZBOTJQMMF9ZP9VCCEGWVKSSQNANP9ZXVCAFGAVBXDJHUMKTKUBZRNKUGJTVUPDZMUK9KZI9EKY9DQDUNA9ZVPCROKYYPISSTDOJZJXMXZWSGZBEPKAUYPBPUTDVQKUYXWYTZXLYTCLLKLJUUJGFFCMZFKDTJET9KEDRMWZSAEFCSXLMRHRLG9EKLRZJ9NZQEONGFQSKSQMWRZXWVEGHCF9ZKGQITDLGFTZLCQMXVGVYIPKSUPPAOIKVWARJHAEJUZOOJFCFJTGQZWROSCEQSITQWZTQDFKECATBWSIRDFVWH9TXXUVEBRRKOTFSGBMGCMZDK9BJRVEUYOVNSLZNNSTWOB9CFNDJHJENDWSTIMMSWD9HSA9LDGTJDJFRGQCWOHYJNMFFLLDMVQEMCMNVQWMOGCEZUTK9MPULTDMKTIVFNVWXDNTRHNREHEDWIZCCRFVM9TXCTLBLYEKTRVVWHKZDOSYOBNLEKPNXMDZGLSVBBNBJIBZYBXZCENKVIXMNGUJUYFGFYFBKELSBZROQWKMESZTWDEEUCSXOWFSNJUGGJANMJFCBJV9GMDDJCAJHWKYQFJLFWZESTSIRBZQWYHGQDFUUWF9F9HXSBZRKBDLRWXSMKW99KPWJOQYPMHB9KWALSVLFNANYSMNWSJLUOCBKAWAJHNWMLPLXHVEPTKBPWLWHMDZ99999999999999999999999999999999999999999999999999999FDKW9AD99A99999999B99999999RJPQGE9AXNBBLPQ9IFJXFJTXJAIGDDXAWXGAVU9PJQNYFADJDIDBTJKSAEBBX9NWJGZVAVXYWXPCEFFH99XJLIVAZYJGPKEHANESX9DORLEIOIFL9WVQBBNUDRZDGJMVSRTSFKOJPIMEXPBCPGKUUEKRGAXOB99999XIFVJWCABR9XQKOWCXJAZDZWMWWBVHROBOPISAWDIJTSFPVAUZNJUMNUCOFIBIAAVIOWDTCNIPRVA9999999999999999999999999999999RSIDBWNOF999999999K99999999POWSRVIO9VMIJ99FKKNKGPNNNNP", + "COSNPEACSPESTKTUS9CSMOCWVUBLERQJQGLLA9AYRRXMOVOBULQZZZJPLNELEX9EVZDAFOHBB9FBAYOTDB9YGJAVTDWNPJVVGEPFJ9QBYLXAYZEETY9GMYQRIRAIPWMJUFMFRSQRCEIOWISMBJKGCIGIIYNMXAYLXZIBNKANT9DSQXPBUPWGUJTOMD9JKSNFZJJFBZYBWRWQIZFOZXQWNJTRZTUPHVEGTAOLMBEPXOB9TCEBNIWXQUHNQJZHAOWGVOOOTSCTEUEMXJUODZP9CDLCMEXAW9YJGVNREENETEYXOAKZVRJVUSCRUSRCNZUGUOLCNHQITWYPJVJGPVULASTONDZRDVCNGJSN9DMMUORCOIBTITSPBXSAVTOHSAXHMGH99APRTQIVCEI9NWYKDOZCCWBNZGGCUCISSMGJHJ9OVSFBZVFMBODKRKSLIDESGFDQITCYFARWYTLFKSPLNVQ9LTYFODCHBAJKCWVZNBLOJH9ABFGDFOXHDNHZILINMYMVZYWTFJJOLADPTZRPJEMGPFQNYTFDGOKMDTDSKPTHIDWLMRSSACZMTHUCWVZHCYSPJRATABZ9JHWEBGTTQORGFOYDWUIYFYGOGGTYGYYI9RPKPMCTYNYKHHNWQKJTDQDHTRUYQPMCDDLVUUHUBPWGPMNQYPKHDHGIUSXJGNUXIYGZRGVYPTTV9KXJDLGVVXRIWIGKZJVCQ9DUWAYNDRKYCGKZDEOKZQCXELSQXLGLAPBDIWRTKGSIRNAZ9XFEGMWOWLPUA9TSGYRDOU9GKY9JJDKCJKKGJCRARGSNECJ9SEVAGPPJMBTMGNWSLNSFBOEYMIQBKOVSCTDIWCOQTHBVTZOFNNIYKKJLAROKIRFFPCLSOWO9BVNNJSXEYLJASPUEFKCEI9SGWADNGS9MZAKFTCAYNRVKGDIGYDTPGJXWFAEFGG9BX9XCNBZGQULH9IPPIQ9VFVAXGELUMCTE9TOFOGCTFYBYEBFOKKAESIIHEWLYLQKWDHPYNELJKMZHDSDSK9ZWFP99FQFLUXGTSVDMGHIFZFUCYTYPVEFEDM9TOHCQVIBXARYWZEKTWEOVSBYZTJZRUFZJCLRBTOWPTKY99CYQTGPMAKFYALIWPEQLUB9RGANTKDDMSDSJOTNPFZCXKJUGTJWWPK9FAJGPJOEBRHL9ZU9XRBDJBVMFIZIZFGKETH9RJUSDIOWDZIXFOJLT9DSIAWSCEYBNOGOAZQPZKVZTBXGACCBVQGFNGXNQFTVPUGUOHNBKTUERLSTDXGRKZSATIRHDBSXARYATBHOLUHCQGPRMQGSJUAXUCWKDXAUGORHFVVVDZQEYYGSV9IUM9VTMHJCIKBQD9TQLLYTZHAKDHPFGYMRGJOEDJKQTBBNFCKDEPYTPWUJEOPRJWHAILMVW9AXS9HAT9GCGRXZEHWNDOVGKGPWFXFC9IKLVZJCTGZUJFRHGL9FZWZYP9TPOWGOUTQ9INYBVTD9N9OV9AM9GYRJ99MRWRWLOAKJXMDTFNS9NZQAYGVZRJ9WVSIDRRQLGKH9VPUVKFWZMSRVVLKXUGJAZESC9LEYDNNDWS9WTWNFPCR9YKPOMIJAVHCRCJVBDFWSTDN9BPJRDMGZFXWTY9GUX9WEPJQMXSECFPREXAJ9DU9OLMNQFTF99QYZOGDUTMBHMNMCNUQTU9FTNEUNBT9NQO9YADGY9C9LQR9DYVJZE9GMMYAOUSWUIVJ9EAR9EEFCO9SZTQ9JQHPIJGXIOQZHOSUSVMHBOLTYSEONVLRISNAN9KJHHFFISTYIHYVAMXLPNALDELETXVSWGUWOIVKNAKZKNNRVNJKE9WMNQBXFGXSYZQQSIODAKINA9CPBOGAYKJ9V9GS9HERXSVYPEILYXC9GURBREBCXRZJVXHUOPGYFLPIFBCOTMHFTAAUQUZVEKOWYFFGSCXDFDBTSGZRMPQR9EYSZYGQETCWFDRXVOXXCUBOQTBQGVLAONSTSLURFYSDPAVNCXNWVPQOUORMVSLYISQUQKXFYYMJSGHQP9VAAZABEAGJMCPIEBZQSYCPS9CNKFBDRDTDA9C9MVKGUISRX9ODVUJNPDWMZ9ESYXJ99ETDGSTJZMZYJEGRUWTORMCLNHLDQ9YIJWPMDYGGJQHMFPJ9VKINJUESTCBJQXFTCJFNUXPZONVKXJRAYGKIWHXSBZRKBDLRWXSMKW99KPWJOQYPMHB9KWALSVLFNANYSMNWSJLUOCBKAWAJHNWMLPLXHVEPTKBPWLWHMD999999999999999999999999999999999999999999999999999999FDKW9AD99B99999999B99999999RJPQGE9AXNBBLPQ9IFJXFJTXJAIGDDXAWXGAVU9PJQNYFADJDIDBTJKSAEBBX9NWJGZVAVXYWXPCEFFH9XIFVJWCABR9XQKOWCXJAZDZWMWWBVHROBOPISAWDIJTSFPVAUZNJUMNUCOFIBIAAVIOWDTCNIPRVA9999FTXZAEQIKGFJABOGCCQGOMKNASGWCQWMNIMRDTHKPJMTKDGMEQNZOAYARIYEWFZYJALRKFODOITTZ9999999999999999999999999999999LLHDBWNOF999999999K99999999POWSRVIO9VMIJ99UMGMTTTPNNNV", }; + List transactions = persistAndMapTxs(trytes); + try { + bundleValidator.validate(tangle, false, snapshot, transactions.get(0).getHash()); + + Mockito.verify(bundleValidator, Mockito.never()).validateBundleTailApproval(Mockito.any(), + Mockito.anyList()); + Mockito.verify(bundleValidator, Mockito.never()).validateBundleTransactionsApproval(Mockito.anyList()); + } catch (Exception e) { + e.printStackTrace(); + } + } + } 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/TangleMockUtils.java b/src/test/java/com/iota/iri/TangleMockUtils.java index 300afb4497..d5a35613c1 100644 --- a/src/test/java/com/iota/iri/TangleMockUtils.java +++ b/src/test/java/com/iota/iri/TangleMockUtils.java @@ -103,7 +103,7 @@ public static List mockValidBundle(Tangle tangle, } Collections.reverse(bundle); - Mockito.when(bundleValidator.validate(Mockito.eq(tangle), Mockito.any(), + Mockito.when(bundleValidator.validate(Mockito.eq(tangle), Mockito.eq(true), Mockito.any(), Mockito.eq(bundle.iterator().next().getHash()))) .thenReturn(bundle); diff --git a/src/test/java/com/iota/iri/TransactionTestUtils.java b/src/test/java/com/iota/iri/TransactionTestUtils.java index 3cba89c1ba..648f134c24 100644 --- a/src/test/java/com/iota/iri/TransactionTestUtils.java +++ b/src/test/java/com/iota/iri/TransactionTestUtils.java @@ -98,6 +98,20 @@ public static TransactionViewModel createBundleHead(int index) { return tx; } + /** + * Generates a transaction with a hash. Transaction last and current index are set to the index provided. + * + * @param index The index to set the transaction to + * @return A transaction which is located on the end of its (nonexistent) bundle + */ + public static TransactionViewModel createBundleHead(int index, Hash hash) { + + TransactionViewModel tx = new TransactionViewModel(getTransactionTrits(), hash); + setLastIndex(tx, index); + setCurrentIndex(tx, index); + return tx; + } + /** * Generates a transaction with the specified trunk and branch, and bundle hash from trunk. * This transaction indices are updated to match the trunk index. diff --git a/src/test/java/com/iota/iri/network/NetworkInjectionConfigurationTest.java b/src/test/java/com/iota/iri/network/NetworkInjectionConfigurationTest.java index c02749a893..6cb4f26b8e 100644 --- a/src/test/java/com/iota/iri/network/NetworkInjectionConfigurationTest.java +++ b/src/test/java/com/iota/iri/network/NetworkInjectionConfigurationTest.java @@ -3,7 +3,7 @@ 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.TransactionValidator; import com.iota.iri.conf.BaseIotaConfig; import com.iota.iri.conf.IotaConfig; import com.iota.iri.network.pipeline.TransactionProcessingPipeline; diff --git a/src/test/java/com/iota/iri/network/pipeline/QuickBundleValidationStageTest.java b/src/test/java/com/iota/iri/network/pipeline/QuickBundleValidationStageTest.java new file mode 100644 index 0000000000..fd8574d72f --- /dev/null +++ b/src/test/java/com/iota/iri/network/pipeline/QuickBundleValidationStageTest.java @@ -0,0 +1,98 @@ +package com.iota.iri.network.pipeline; + +import com.iota.iri.BundleValidator; +import com.iota.iri.controllers.TransactionViewModel; +import com.iota.iri.network.neighbor.Neighbor; +import com.iota.iri.service.snapshot.SnapshotProvider; +import com.iota.iri.service.validation.TransactionValidator; +import com.iota.iri.storage.Tangle; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +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; + +public class QuickBundleValidationStageTest { + + @Rule + public MockitoRule mockitoRule = MockitoJUnit.rule(); + + @Mock + BundleValidator bundleValidator; + @Mock + TransactionValidator transactionValidator; + @Mock + Tangle tangle; + @Mock + SnapshotProvider snapshotProvider; + @Mock + private Neighbor neighbor; + @Mock + TransactionViewModel tvm; + + @Test + public void propagatesSolidTailAndValidBundle() throws Exception { + List bundleTransactions = new ArrayList() { + + { + add(tvm); + } + }; + Mockito.when(tvm.isSolid()).thenReturn(true); + Mockito.when(tvm.getCurrentIndex()).thenReturn(0L); + Mockito.when(bundleValidator.validate(tangle, true, snapshotProvider.getInitialSnapshot(), tvm.getHash())) + .thenReturn(bundleTransactions); + + QuickBundleValidationStage stage = new QuickBundleValidationStage(tangle, snapshotProvider, bundleValidator, + transactionValidator); + QuickBundleValidationPayload payload = new QuickBundleValidationPayload(neighbor, tvm); + ProcessingContext ctx = new ProcessingContext(null, payload); + stage.process(ctx); + + Mockito.verify(transactionValidator).addSolidTransaction(tvm.getHash()); + } + + @Test + public void shouldNotPropagateNonSolidTail() throws Exception { + Mockito.when(tvm.isSolid()).thenReturn(false); + Mockito.when(tvm.getCurrentIndex()).thenReturn(0L); + QuickBundleValidationStage stage = new QuickBundleValidationStage(tangle, snapshotProvider, bundleValidator, + transactionValidator); + QuickBundleValidationPayload payload = new QuickBundleValidationPayload(neighbor, tvm); + ProcessingContext ctx = new ProcessingContext(null, payload); + stage.process(ctx); + Mockito.verify(transactionValidator, Mockito.never()).addSolidTransaction(tvm.getHash()); + } + + @Test + public void shouldNotPropagateNonTail() throws Exception { + Mockito.when(tvm.isSolid()).thenReturn(false); + Mockito.when(tvm.getCurrentIndex()).thenReturn(1L); + QuickBundleValidationStage stage = new QuickBundleValidationStage(tangle, snapshotProvider, bundleValidator, + transactionValidator); + QuickBundleValidationPayload payload = new QuickBundleValidationPayload(neighbor, tvm); + ProcessingContext ctx = new ProcessingContext(null, payload); + stage.process(ctx); + Mockito.verify(transactionValidator, Mockito.never()).addSolidTransaction(tvm.getHash()); + } + + @Test + public void shouldNotPropagateInvalidBundle() throws Exception { + Mockito.when(tvm.isSolid()).thenReturn(false); + Mockito.when(tvm.getCurrentIndex()).thenReturn(1L); + Mockito.when(bundleValidator.validate(tangle, true, snapshotProvider.getInitialSnapshot(), tvm.getHash())) + .thenReturn(Collections.emptyList()); + QuickBundleValidationStage stage = new QuickBundleValidationStage(tangle, snapshotProvider, bundleValidator, + transactionValidator); + QuickBundleValidationPayload payload = new QuickBundleValidationPayload(neighbor, tvm); + ProcessingContext ctx = new ProcessingContext(null, payload); + stage.process(ctx); + Mockito.verify(transactionValidator, Mockito.never()).addSolidTransaction(tvm.getHash()); + } +} 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..8fa51f6b58 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.TransactionValidator; import com.iota.iri.controllers.TransactionViewModel; import com.iota.iri.network.TransactionRequester; import com.iota.iri.network.neighbor.Neighbor; @@ -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.QUICK_BUNDLE_VALIDATION, 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()); + QuickBundleValidationPayload quickBundleValidationPayload = (QuickBundleValidationPayload) ctx.getPayload(); + assertEquals("neighbor is still the same", neighbor, quickBundleValidationPayload.getOriginNeighbor()); + assertEquals("tvm is still the same", tvm, quickBundleValidationPayload.getTransactionViewModel()); } @Test @@ -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.QUICK_BUNDLE_VALIDATION, 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()); + QuickBundleValidationPayload quickBundleValidationPayload = (QuickBundleValidationPayload) ctx.getPayload(); + assertEquals("neighbor should still be the same", neighbor, quickBundleValidationPayload.getOriginNeighbor()); + assertEquals("tvm should still be the same", tvm, quickBundleValidationPayload.getTransactionViewModel()); } } \ No newline at end of file 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..ed0a25b579 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.BundleValidator; +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; @@ -35,6 +36,9 @@ public class TransactionProcessingPipelineTest { @Mock private TransactionValidator transactionValidator; + @Mock + private BundleValidator bundleValidator; + @Mock private SnapshotProvider snapshotProvider; @@ -65,6 +69,9 @@ public class TransactionProcessingPipelineTest { @Mock private BroadcastStage broadcastStage; + @Mock + private QuickBundleValidationStage quickBundleValidationStage; + @Mock private HashingStage hashingStage; @@ -86,6 +93,9 @@ public class TransactionProcessingPipelineTest { @Mock private ProcessingContext receivedCtx; + @Mock + private ProcessingContext quickBundleValidationCtx; + @Mock private ProcessingContext broadcastCtx; @@ -107,6 +117,7 @@ private void injectMockedStagesIntoPipeline(TransactionProcessingPipeline pipeli pipeline.setHashingStage(hashingStage); pipeline.setReplyStage(replyStage); pipeline.setValidationStage(validationStage); + pipeline.setQuickBundleValidationStage(quickBundleValidationStage); } @Test @@ -114,7 +125,7 @@ public void processingAValidNewTransactionFlowsThroughTheEntirePipeline() throws TransactionProcessingPipeline pipeline = new TransactionProcessingPipelineImpl(neighborRouter, nodeConfig, transactionValidator, tangle, snapshotProvider, tipsViewModel, latestMilestoneTracker, - transactionRequester); + transactionRequester, bundleValidator); // inject mocks injectMockedStagesIntoPipeline(pipeline); @@ -134,9 +145,15 @@ public void processingAValidNewTransactionFlowsThroughTheEntirePipeline() throws .thenReturn(TransactionProcessingPipeline.Stage.MULTIPLE); Mockito.when(divergeToReplyAndReceivedCtx.getPayload()).thenReturn(divergePayload); - // mock received + //mock quick bundle validation + Mockito.when(quickBundleValidationCtx.getNextStage()).thenReturn(TransactionProcessingPipeline.Stage.QUICK_BUNDLE_VALIDATION); + Mockito.when(quickBundleValidationStage.process(quickBundleValidationCtx)).thenReturn(broadcastCtx); + + //mock received + Mockito.when(receivedStage.process(receivedCtx)).thenReturn(quickBundleValidationCtx); + + // mock broadcast Mockito.when(broadcastCtx.getNextStage()).thenReturn(TransactionProcessingPipeline.Stage.BROADCAST); - Mockito.when(receivedStage.process(receivedCtx)).thenReturn(broadcastCtx); pipeline.start(); @@ -151,6 +168,7 @@ public void processingAValidNewTransactionFlowsThroughTheEntirePipeline() throws Mockito.verify(hashingStage).process(Mockito.any()); Mockito.verify(validationStage).process(Mockito.any()); Mockito.verify(receivedStage).process(Mockito.any()); + Mockito.verify(quickBundleValidationStage).process(Mockito.any()); Mockito.verify(replyStage).process(Mockito.any()); Mockito.verify(broadcastStage).process(Mockito.any()); } @@ -159,7 +177,7 @@ public void processingAValidNewTransactionFlowsThroughTheEntirePipeline() throws public void processingAKnownTransactionOnlyFlowsToTheReplyStage() throws InterruptedException { TransactionProcessingPipeline pipeline = new TransactionProcessingPipelineImpl(neighborRouter, nodeConfig, transactionValidator, tangle, snapshotProvider, tipsViewModel, latestMilestoneTracker, - transactionRequester); + transactionRequester, bundleValidator); // inject mocks pipeline.setPreProcessStage(preProcessStage); @@ -193,7 +211,7 @@ public void processingAValidNewTransactionNotOriginatingFromANeighborFlowsThroug throws InterruptedException { TransactionProcessingPipeline pipeline = new TransactionProcessingPipelineImpl(neighborRouter, nodeConfig, transactionValidator, tangle, snapshotProvider, tipsViewModel, latestMilestoneTracker, - transactionRequester); + transactionRequester, bundleValidator); // inject mocks injectMockedStagesIntoPipeline(pipeline); @@ -210,9 +228,15 @@ public void processingAValidNewTransactionNotOriginatingFromANeighborFlowsThroug Mockito.when(validationStage.process(validationCtx)).thenReturn(receivedCtx); Mockito.when(receivedCtx.getNextStage()).thenReturn(TransactionProcessingPipeline.Stage.RECEIVED); - // mock received + //mock quick bundle validation + Mockito.when(quickBundleValidationCtx.getNextStage()).thenReturn(TransactionProcessingPipeline.Stage.QUICK_BUNDLE_VALIDATION); + Mockito.when(quickBundleValidationStage.process(quickBundleValidationCtx)).thenReturn(broadcastCtx); + + //mock received + Mockito.when(receivedStage.process(receivedCtx)).thenReturn(quickBundleValidationCtx); + + // mock broadcast Mockito.when(broadcastCtx.getNextStage()).thenReturn(TransactionProcessingPipeline.Stage.BROADCAST); - Mockito.when(receivedStage.process(receivedCtx)).thenReturn(broadcastCtx); pipeline.start(); @@ -231,6 +255,7 @@ public void processingAValidNewTransactionNotOriginatingFromANeighborFlowsThroug Mockito.verify(hashingStage).process(Mockito.any()); Mockito.verify(validationStage).process(Mockito.any()); Mockito.verify(receivedStage).process(Mockito.any()); + Mockito.verify(quickBundleValidationStage).process(Mockito.any()); Mockito.verify(broadcastStage).process(Mockito.any()); } @@ -238,7 +263,7 @@ public void processingAValidNewTransactionNotOriginatingFromANeighborFlowsThroug public void anInvalidNewTransactionStopsBeingProcessedAfterTheValidationStage() throws InterruptedException { TransactionProcessingPipeline pipeline = new TransactionProcessingPipelineImpl(neighborRouter, nodeConfig, transactionValidator, tangle, snapshotProvider, tipsViewModel, latestMilestoneTracker, - transactionRequester); + transactionRequester, bundleValidator); // inject mocks injectMockedStagesIntoPipeline(pipeline); @@ -269,6 +294,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(quickBundleValidationStage, 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..9643959f54 100644 --- a/src/test/java/com/iota/iri/service/APITest.java +++ b/src/test/java/com/iota/iri/service/APITest.java @@ -1,6 +1,6 @@ package com.iota.iri.service; -import com.iota.iri.TransactionValidator; +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; diff --git a/src/test/java/com/iota/iri/service/ledger/impl/LedgerServiceImplTest.java b/src/test/java/com/iota/iri/service/ledger/impl/LedgerServiceImplTest.java index 72dbb53539..59dd13e500 100644 --- a/src/test/java/com/iota/iri/service/ledger/impl/LedgerServiceImplTest.java +++ b/src/test/java/com/iota/iri/service/ledger/impl/LedgerServiceImplTest.java @@ -71,7 +71,7 @@ public void generateBalanceDiffWithPersistsSpentAddresses() throws Exception { int milestoneIndex = 1; when(milestoneService.isTransactionConfirmed(tailTx, milestoneIndex)).thenReturn(false); when(snapshotProvider.getInitialSnapshot().getSolidEntryPoints()).thenReturn(Collections.emptyMap()); - ledgerService.generateBalanceDiff(new HashSet<>(), tailTx.getHash(), milestoneIndex); + ledgerService.generateBalanceDiff(new HashSet<>(), tailTx.getHash(), milestoneIndex, true); verify(spentAddressesService, times(1)).persistValidatedSpentAddressesAsync(eq(bundle)); } } \ No newline at end of file diff --git a/src/test/java/com/iota/iri/TransactionValidatorTest.java b/src/test/java/com/iota/iri/service/validation/TransactionValidatorTest.java similarity index 98% rename from src/test/java/com/iota/iri/TransactionValidatorTest.java rename to src/test/java/com/iota/iri/service/validation/TransactionValidatorTest.java index ad35624978..99a39ea952 100644 --- a/src/test/java/com/iota/iri/TransactionValidatorTest.java +++ b/src/test/java/com/iota/iri/service/validation/TransactionValidatorTest.java @@ -1,5 +1,6 @@ -package com.iota.iri; +package com.iota.iri.service.validation; +import com.iota.iri.TransactionTestUtils; import com.iota.iri.conf.MainnetConfig; import com.iota.iri.conf.ProtocolConfig; @@ -11,6 +12,7 @@ import com.iota.iri.service.snapshot.SnapshotProvider; import com.iota.iri.service.snapshot.impl.SnapshotMockUtils; +import com.iota.iri.service.validation.TransactionValidator; import com.iota.iri.storage.Tangle; import com.iota.iri.storage.rocksDB.RocksDBPersistenceProvider; import com.iota.iri.utils.Converter;