88import com .iota .iri .utils .Converter ;
99
1010import java .util .*;
11+ import com .google .common .annotations .VisibleForTesting ;
1112
1213/**
1314 * Validates bundles.
@@ -55,22 +56,33 @@ public enum Validity {
5556 */
5657 public static final int MODE_VALIDATE_SEMANTICS = 1 << 2 ;
5758
59+ /**
60+ * Instructs the validation code to validate all transactions within the bundle approve via their branch the trunk
61+ * transaction of the head transaction
62+ */
63+ public static final int MODE_VALIDATE_BUNDLE_TX_APPROVAL = 1 << 3 ;
64+
65+ /**
66+ * Instructs the validation code to validate that the bundle only approves tail txs.
67+ */
68+ public static final int MODE_VALIDATE_TAIL_APPROVAL = 1 << 4 ;
69+
5870 /**
5971 * Instructs the validation code to fully validate the semantics, bundle hash and signatures of the given bundle.
6072 */
61- public static final int MODE_VALIDATE_ALL = MODE_VALIDATE_SIGNATURES | MODE_VALIDATE_BUNDLE_HASH | MODE_VALIDATE_SEMANTICS ;
73+ 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 ;
6274
6375 /**
6476 * Instructs the validation code to skip checking the bundle's already computed validity and instead to proceed to
6577 * validate the bundle further.
6678 */
67- public static final int MODE_SKIP_CACHED_VALIDITY = 1 << 3 ;
79+ public static final int MODE_SKIP_CACHED_VALIDITY = 1 << 5 ;
6880
6981 /**
7082 * Instructs the validation code to skip checking whether the tail transaction is present or a tail transaction was
7183 * given as the start transaction.
7284 */
73- public static final int MODE_SKIP_TAIL_TX_EXISTENCE = 1 << 4 ;
85+ public static final int MODE_SKIP_TAIL_TX_EXISTENCE = 1 << 6 ;
7486
7587 /**
7688 * Fetches a bundle of transactions identified by the {@code tailHash} and validates the transactions. Bundle is a
@@ -85,6 +97,8 @@ public enum Validity {
8597 * <li>Total bundle value is 0 (inputs and outputs are balanced)</li>
8698 * <li>Recalculate the bundle hash by absorbing and squeezing the transactions' essence</li>
8799 * <li>Validate the signature on input transactions</li>
100+ * <li>The bundle must only approve tail transactions</li>
101+ * <li>All transactions within the bundle approve via their branch the trunk transaction of the head transaction.</li>
88102 * </ol>
89103 * <p>
90104 * As well as the following syntactic checks:
@@ -96,9 +110,11 @@ public enum Validity {
96110 * we lose the last trit in the process</li>
97111 * </ol>
98112 *
99- * @param tangle used to fetch the bundle's transactions from the persistence layer
100- * @param initialSnapshot the initial snapshot that defines the genesis for our ledger state
101- * @param tailHash the hash of the last transaction in a bundle.
113+ * @param tangle used to fetch the bundle's transactions from the persistence layer
114+ * @param enforceExtraRules true if enforce {@link #validateBundleTailApproval(Tangle, List)} and
115+ * {@link #validateBundleTransactionsApproval(List)}
116+ * @param initialSnapshot the initial snapshot that defines the genesis for our ledger state
117+ * @param tailHash the hash of the last transaction in a bundle.
102118 * @return A list of transactions of the bundle contained in another list. If the bundle is valid then the tail
103119 * transaction's {@link TransactionViewModel#getValidity()} will return 1, else {@link
104120 * TransactionViewModel#getValidity()} will return -1. If the bundle is invalid then an empty list will be
@@ -108,9 +124,32 @@ public enum Validity {
108124 * validate it again.
109125 * </p>
110126 */
111- public List <TransactionViewModel > validate (Tangle tangle , Snapshot initialSnapshot , Hash tailHash ) throws Exception {
127+ public List <TransactionViewModel > validate (Tangle tangle , boolean enforceExtraRules , Snapshot initialSnapshot ,
128+ Hash tailHash ) throws Exception {
129+ int mode = getMode (enforceExtraRules );
130+ return validate (tangle , initialSnapshot , tailHash , mode );
131+ }
132+
133+ /**
134+ * Does {@link #validate(Tangle, boolean, Snapshot, Hash)} but with an option of skipping some checks according to
135+ * the give {@code mode}
136+ *
137+ * @param tangle used to fetch the bundle's transactions from the persistence layer
138+ * @param initialSnapshot the initial snapshot that defines the genesis for our ledger state
139+ * @param tailHash the hash of the last transaction in a bundle.
140+ * @param mode flags that specify which validation checks to perform
141+ * @return A list of transactions of the bundle contained in another list. If the bundle is valid then the tail
142+ * transaction's {@link TransactionViewModel#getValidity()} will return 1, else
143+ * {@link TransactionViewModel#getValidity()} will return -1. If the bundle is invalid then an empty list
144+ * will be returned.
145+ * @throws Exception if a persistence error occurred
146+ * @implNote if {@code tailHash} was already invalidated/validated by a previous call to this method then we don't
147+ * validate it again.
148+ * @see #validate(Tangle, boolean, Snapshot, Hash)
149+ */
150+ private List <TransactionViewModel > validate (Tangle tangle , Snapshot initialSnapshot , Hash tailHash , int mode ) throws Exception {
112151 List <TransactionViewModel > bundleTxs = new LinkedList <>();
113- switch (validate (tangle , tailHash , MODE_VALIDATE_ALL , bundleTxs )) {
152+ switch (validate (tangle , tailHash , mode , bundleTxs )) {
114153 case VALID :
115154 if (bundleTxs .get (0 ).getValidity () != 1 ) {
116155 bundleTxs .get (0 ).setValidity (tangle , initialSnapshot , 1 );
@@ -126,7 +165,14 @@ public List<TransactionViewModel> validate(Tangle tangle, Snapshot initialSnapsh
126165 }
127166 }
128167
129- private static boolean hasMode (int mode , int has ) {
168+ private static int getMode (boolean enforceExtraRules ) {
169+ if (enforceExtraRules ) {
170+ return MODE_VALIDATE_ALL ;
171+ }
172+ return MODE_VALIDATE_SIGNATURES | MODE_VALIDATE_BUNDLE_HASH | MODE_VALIDATE_SEMANTICS ;
173+ }
174+
175+ private static boolean hasMode (int mode , int has ) {
130176 return (mode & has ) == has ;
131177 }
132178
@@ -147,7 +193,9 @@ private static boolean hasMode(int mode, int has) {
147193 * @return whether the validation criteria were passed or not
148194 * @throws Exception if an error occurred in the persistence layer
149195 */
150- public static Validity validate (Tangle tangle , Hash startTxHash , int validationMode , List <TransactionViewModel > bundleTxs ) throws Exception {
196+ @ VisibleForTesting
197+ Validity validate (Tangle tangle , Hash startTxHash , int validationMode , List <TransactionViewModel > bundleTxs )
198+ throws Exception {
151199 TransactionViewModel startTx = TransactionViewModel .fromHash (tangle , startTxHash );
152200 if (startTx == null || (!hasMode (validationMode , MODE_SKIP_TAIL_TX_EXISTENCE ) &&
153201 (startTx .getCurrentIndex () != 0 || startTx .getValidity () == -1 ))) {
@@ -160,7 +208,8 @@ public static Validity validate(Tangle tangle, Hash startTxHash, int validationM
160208 !hasMode (validationMode , MODE_VALIDATE_SEMANTICS ));
161209
162210 // check the semantics of the bundle: total sum, semantics per tx (current/last index), missing txs, supply
163- Validity bundleSemanticsValidity = validateBundleSemantics (startTx , bundleTxsMapping , bundleTxs , validationMode );
211+ Validity bundleSemanticsValidity = validateBundleSemantics (startTx , bundleTxsMapping , bundleTxs ,
212+ validationMode );
164213 if (hasMode (validationMode , MODE_VALIDATE_SEMANTICS ) && bundleSemanticsValidity != Validity .VALID ) {
165214 return bundleSemanticsValidity ;
166215 }
@@ -177,6 +226,21 @@ public static Validity validate(Tangle tangle, Hash startTxHash, int validationM
177226 return bundleHashValidity ;
178227 }
179228
229+ //verify that the bundle only approves tail txs
230+ if (hasMode (validationMode , MODE_VALIDATE_TAIL_APPROVAL )) {
231+ Validity bundleTailApprovalValidity = validateBundleTailApproval (tangle , bundleTxs );
232+ if (bundleTailApprovalValidity != Validity .VALID ) {
233+ return bundleTailApprovalValidity ;
234+ }
235+ }
236+
237+ //verify all transactions within the bundle approve via their branch the trunk transaction of the head transaction
238+ if (hasMode (validationMode , MODE_VALIDATE_BUNDLE_TX_APPROVAL )) {
239+ Validity bundleTransactionsApprovalValidity = validateBundleTransactionsApproval (bundleTxs );
240+ if (bundleTransactionsApprovalValidity != Validity .VALID ) {
241+ return bundleTransactionsApprovalValidity ;
242+ }
243+ }
180244
181245 // verify the signatures of input transactions
182246 if (hasMode (validationMode , MODE_VALIDATE_SIGNATURES )) {
@@ -198,7 +262,8 @@ public static Validity validate(Tangle tangle, Hash startTxHash, int validationM
198262 * @param bundleTxs an empty list which gets filled with the transactions in order of trunk ordering
199263 * @return whether the bundle is semantically valid
200264 */
201- public static Validity validateBundleSemantics (TransactionViewModel startTx , Map <Hash , TransactionViewModel > bundleTxsMapping , List <TransactionViewModel > bundleTxs ) {
265+ public Validity validateBundleSemantics (TransactionViewModel startTx ,
266+ Map <Hash , TransactionViewModel > bundleTxsMapping , List <TransactionViewModel > bundleTxs ) {
202267 return validateBundleSemantics (startTx , bundleTxsMapping , bundleTxs , MODE_VALIDATE_SEMANTICS );
203268 }
204269
@@ -221,7 +286,9 @@ public static Validity validateBundleSemantics(TransactionViewModel startTx, Map
221286 * @param validationMode the used validation mode
222287 * @return whether the bundle is semantically valid
223288 */
224- private static Validity validateBundleSemantics (TransactionViewModel startTx , Map <Hash , TransactionViewModel > bundleTxsMapping , List <TransactionViewModel > bundleTxs , int validationMode ) {
289+ private Validity validateBundleSemantics (TransactionViewModel startTx ,
290+ Map <Hash , TransactionViewModel > bundleTxsMapping , List <TransactionViewModel > bundleTxs ,
291+ int validationMode ) {
225292 TransactionViewModel tvm = startTx ;
226293 final long lastIndex = tvm .lastIndex ();
227294 long bundleValue = 0 ;
@@ -379,6 +446,39 @@ public static boolean isInconsistent(Collection<TransactionViewModel> transactio
379446 return (sum != 0 || transactionViewModels .isEmpty ());
380447 }
381448
449+ /**
450+ * A bundle is invalid if The branch transaction hash of the non head transactions within a bundle, is not the same
451+ * as the trunk transaction hash of the head transaction.
452+ *
453+ * @param bundleTxs list of transactions that are in a bundle.
454+ * @return Whether the bundle tx chain is valid.
455+ */
456+ @ VisibleForTesting
457+ Validity validateBundleTransactionsApproval (List <TransactionViewModel > bundleTxs ) {
458+ Hash headTrunkTransactionHash = bundleTxs .get (bundleTxs .size () - 1 ).getTrunkTransactionHash ();
459+ for (int i = 0 ; i < bundleTxs .size () - 1 ; i ++){
460+ if (!bundleTxs .get (i ).getBranchTransactionHash ().equals (headTrunkTransactionHash )){
461+ return Validity .INVALID ;
462+ }
463+ }
464+ return Validity .VALID ;
465+ }
466+
467+ /**
468+ * A bundle is invalid if the trunk and branch transactions approved by the bundle are non tails.
469+ *
470+ * @param bundleTxs The txs in the bundle.
471+ * @return Whether the bundle approves only tails.
472+ */
473+ @ VisibleForTesting
474+ Validity validateBundleTailApproval (Tangle tangle , List <TransactionViewModel > bundleTxs ) throws Exception {
475+ TransactionViewModel headTx = bundleTxs .get (bundleTxs .size () - 1 );
476+ TransactionViewModel bundleTrunkTvm = headTx .getTrunkTransaction (tangle );
477+ TransactionViewModel bundleBranchTvm = headTx .getBranchTransaction (tangle );
478+ return bundleTrunkTvm != null && bundleBranchTvm != null && bundleBranchTvm .getCurrentIndex () == 0
479+ && bundleTrunkTvm .getCurrentIndex () == 0 ? Validity .VALID : Validity .INVALID ;
480+ }
481+
382482 /**
383483 * Traverses down the given {@code tail} trunk until all transactions that belong to the same bundle (identified by
384484 * the bundle hash) are found and loaded.
0 commit comments