Skip to content
This repository was archived by the owner on Aug 23, 2020. It is now read-only.

Commit a51ae09

Browse files
author
Gal Rogozinski
authored
Check validity rules on tip-sel and check-consistency only (#1802)
* Enforce rules via flag * make bundle validator more testable * added test * fix formatting * fix mockito exception
1 parent 450b728 commit a51ae09

File tree

10 files changed

+189
-57
lines changed

10 files changed

+189
-57
lines changed

src/main/java/com/iota/iri/BundleValidator.java

Lines changed: 63 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import com.iota.iri.utils.Converter;
99

1010
import java.util.*;
11+
import com.google.common.annotations.VisibleForTesting;
1112

1213
/**
1314
* Validates bundles.
@@ -109,9 +110,11 @@ public enum Validity {
109110
* we lose the last trit in the process</li>
110111
* </ol>
111112
*
112-
* @param tangle used to fetch the bundle's transactions from the persistence layer
113-
* @param initialSnapshot the initial snapshot that defines the genesis for our ledger state
114-
* @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.
115118
* @return A list of transactions of the bundle contained in another list. If the bundle is valid then the tail
116119
* transaction's {@link TransactionViewModel#getValidity()} will return 1, else {@link
117120
* TransactionViewModel#getValidity()} will return -1. If the bundle is invalid then an empty list will be
@@ -121,9 +124,32 @@ public enum Validity {
121124
* validate it again.
122125
* </p>
123126
*/
124-
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 {
125151
List<TransactionViewModel> bundleTxs = new LinkedList<>();
126-
switch (validate(tangle, tailHash, MODE_VALIDATE_ALL, bundleTxs)) {
152+
switch (validate(tangle, tailHash, mode, bundleTxs)) {
127153
case VALID:
128154
if (bundleTxs.get(0).getValidity() != 1) {
129155
bundleTxs.get(0).setValidity(tangle, initialSnapshot, 1);
@@ -139,7 +165,14 @@ public List<TransactionViewModel> validate(Tangle tangle, Snapshot initialSnapsh
139165
}
140166
}
141167

142-
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) {
143176
return (mode & has) == has;
144177
}
145178

@@ -160,7 +193,9 @@ private static boolean hasMode(int mode, int has) {
160193
* @return whether the validation criteria were passed or not
161194
* @throws Exception if an error occurred in the persistence layer
162195
*/
163-
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 {
164199
TransactionViewModel startTx = TransactionViewModel.fromHash(tangle, startTxHash);
165200
if (startTx == null || (!hasMode(validationMode, MODE_SKIP_TAIL_TX_EXISTENCE) &&
166201
(startTx.getCurrentIndex() != 0 || startTx.getValidity() == -1))) {
@@ -173,7 +208,8 @@ public static Validity validate(Tangle tangle, Hash startTxHash, int validationM
173208
!hasMode(validationMode, MODE_VALIDATE_SEMANTICS));
174209

175210
// check the semantics of the bundle: total sum, semantics per tx (current/last index), missing txs, supply
176-
Validity bundleSemanticsValidity = validateBundleSemantics(startTx, bundleTxsMapping, bundleTxs, validationMode);
211+
Validity bundleSemanticsValidity = validateBundleSemantics(startTx, bundleTxsMapping, bundleTxs,
212+
validationMode);
177213
if (hasMode(validationMode, MODE_VALIDATE_SEMANTICS) && bundleSemanticsValidity != Validity.VALID) {
178214
return bundleSemanticsValidity;
179215
}
@@ -191,15 +227,19 @@ public static Validity validate(Tangle tangle, Hash startTxHash, int validationM
191227
}
192228

193229
//verify that the bundle only approves tail txs
194-
Validity bundleTailApprovalValidity = validateBundleTailApproval(tangle, bundleTxs);
195-
if(hasMode(validationMode, MODE_VALIDATE_TAIL_APPROVAL) && bundleTailApprovalValidity != Validity.VALID){
196-
return bundleTailApprovalValidity;
230+
if (hasMode(validationMode, MODE_VALIDATE_TAIL_APPROVAL)) {
231+
Validity bundleTailApprovalValidity = validateBundleTailApproval(tangle, bundleTxs);
232+
if (bundleTailApprovalValidity != Validity.VALID) {
233+
return bundleTailApprovalValidity;
234+
}
197235
}
198236

199237
//verify all transactions within the bundle approve via their branch the trunk transaction of the head transaction
200-
Validity bundleTransactionsApprovalValidity = validateBundleTransactionsApproval(bundleTxs);
201-
if(hasMode(validationMode, MODE_VALIDATE_BUNDLE_TX_APPROVAL) && bundleTransactionsApprovalValidity != Validity.VALID){
202-
return bundleTransactionsApprovalValidity;
238+
if (hasMode(validationMode, MODE_VALIDATE_BUNDLE_TX_APPROVAL)) {
239+
Validity bundleTransactionsApprovalValidity = validateBundleTransactionsApproval(bundleTxs);
240+
if (bundleTransactionsApprovalValidity != Validity.VALID) {
241+
return bundleTransactionsApprovalValidity;
242+
}
203243
}
204244

205245
// verify the signatures of input transactions
@@ -222,7 +262,8 @@ public static Validity validate(Tangle tangle, Hash startTxHash, int validationM
222262
* @param bundleTxs an empty list which gets filled with the transactions in order of trunk ordering
223263
* @return whether the bundle is semantically valid
224264
*/
225-
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) {
226267
return validateBundleSemantics(startTx, bundleTxsMapping, bundleTxs, MODE_VALIDATE_SEMANTICS);
227268
}
228269

@@ -245,7 +286,9 @@ public static Validity validateBundleSemantics(TransactionViewModel startTx, Map
245286
* @param validationMode the used validation mode
246287
* @return whether the bundle is semantically valid
247288
*/
248-
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) {
249292
TransactionViewModel tvm = startTx;
250293
final long lastIndex = tvm.lastIndex();
251294
long bundleValue = 0;
@@ -410,7 +453,8 @@ public static boolean isInconsistent(Collection<TransactionViewModel> transactio
410453
* @param bundleTxs list of transactions that are in a bundle.
411454
* @return Whether the bundle tx chain is valid.
412455
*/
413-
public static Validity validateBundleTransactionsApproval(List<TransactionViewModel> bundleTxs){
456+
@VisibleForTesting
457+
Validity validateBundleTransactionsApproval(List<TransactionViewModel> bundleTxs) {
414458
Hash headTrunkTransactionHash = bundleTxs.get(bundleTxs.size() - 1).getTrunkTransactionHash();
415459
for(int i = 0; i < bundleTxs.size() - 1; i++){
416460
if(!bundleTxs.get(i).getBranchTransactionHash().equals(headTrunkTransactionHash)){
@@ -426,7 +470,8 @@ public static Validity validateBundleTransactionsApproval(List<TransactionViewMo
426470
* @param bundleTxs The txs in the bundle.
427471
* @return Whether the bundle approves only tails.
428472
*/
429-
public static Validity validateBundleTailApproval(Tangle tangle, List<TransactionViewModel> bundleTxs) throws Exception {
473+
@VisibleForTesting
474+
Validity validateBundleTailApproval(Tangle tangle, List<TransactionViewModel> bundleTxs) throws Exception {
430475
TransactionViewModel headTx = bundleTxs.get(bundleTxs.size() - 1);
431476
TransactionViewModel bundleTrunkTvm = headTx.getTrunkTransaction(tangle);
432477
TransactionViewModel bundleBranchTvm = headTx.getBranchTransaction(tangle);

src/main/java/com/iota/iri/service/API.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -389,10 +389,14 @@ private AbstractResponse checkConsistencyStatement(List<String> transactionsList
389389
state = false;
390390
info = "tails are not solid (missing a referenced tx): " + transaction;
391391
break;
392-
} else if (bundleValidator.validate(tangle, snapshotProvider.getInitialSnapshot(), txVM.getHash()).isEmpty()) {
393-
state = false;
394-
info = "tails are not consistent (bundle is invalid): " + transaction;
395-
break;
392+
} else {
393+
if (bundleValidator
394+
.validate(tangle, true, snapshotProvider.getInitialSnapshot(), txVM.getHash())
395+
.isEmpty()) {
396+
state = false;
397+
info = "tails are not consistent (bundle is invalid): " + transaction;
398+
break;
399+
}
396400
}
397401
}
398402

src/main/java/com/iota/iri/service/ledger/LedgerService.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.iota.iri.controllers.MilestoneViewModel;
44
import com.iota.iri.model.Hash;
5+
import com.iota.iri.storage.Tangle;
56

67
import java.util.List;
78
import java.util.Map;
@@ -99,12 +100,16 @@ public interface LedgerService {
99100
* </p>
100101
*
101102
* @param visitedTransactions a set of transaction hashes that shall be considered to be visited already
102-
* @param startTransaction the transaction that marks the start of the dag traversal and that has its approvees
103-
* examined
103+
* @param startTransaction the transaction that marks the start of the dag traversal and that has its approvees
104+
* examined
105+
* @param enforceExtraRules enforce {@link com.iota.iri.BundleValidator#validateBundleTransactionsApproval(List)}
106+
* and {@link com.iota.iri.BundleValidator#validateBundleTailApproval(Tangle, List)}.
107+
* Enforcing them may break backwards compatibility.
104108
* @return a map of the balance changes (addresses associated to their balance) or {@code null} if the balance could
105109
* not be generated due to inconsistencies
106110
* @throws LedgerException if anything unexpected happens while generating the balance changes
107111
*/
108-
Map<Hash, Long> generateBalanceDiff(Set<Hash> visitedTransactions, Hash startTransaction, int milestoneIndex)
112+
Map<Hash, Long> generateBalanceDiff(Set<Hash> visitedTransactions, Hash startTransaction, int milestoneIndex,
113+
boolean enforceExtraRules)
109114
throws LedgerException;
110115
}

src/main/java/com/iota/iri/service/ledger/impl/LedgerServiceImpl.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ public boolean isBalanceDiffConsistent(Set<Hash> approvedHashes, Map<Hash, Long>
133133
}
134134
Set<Hash> visitedHashes = new HashSet<>(approvedHashes);
135135
Map<Hash, Long> currentState = generateBalanceDiff(visitedHashes, tip,
136-
snapshotProvider.getLatestSnapshot().getIndex());
136+
snapshotProvider.getLatestSnapshot().getIndex(), true);
137137
if (currentState == null) {
138138
return false;
139139
}
@@ -152,7 +152,8 @@ public boolean isBalanceDiffConsistent(Set<Hash> approvedHashes, Map<Hash, Long>
152152
}
153153

154154
@Override
155-
public Map<Hash, Long> generateBalanceDiff(Set<Hash> visitedTransactions, Hash startTransaction, int milestoneIndex)
155+
public Map<Hash, Long> generateBalanceDiff(Set<Hash> visitedTransactions, Hash startTransaction, int milestoneIndex,
156+
boolean enforceExtraRules)
156157
throws LedgerException {
157158

158159
Map<Hash, Long> state = new HashMap<>();
@@ -176,7 +177,8 @@ public Map<Hash, Long> generateBalanceDiff(Set<Hash> visitedTransactions, Hash s
176177
if (!milestoneService.isTransactionConfirmed(transactionViewModel, milestoneIndex)) {
177178

178179
final List<TransactionViewModel> bundleTransactions = bundleValidator.validate(tangle,
179-
snapshotProvider.getInitialSnapshot(), transactionViewModel.getHash());
180+
enforceExtraRules, snapshotProvider.getInitialSnapshot(),
181+
transactionViewModel.getHash());
180182

181183
if (bundleTransactions.isEmpty()) {
182184
return null;
@@ -257,7 +259,7 @@ private boolean generateStateDiff(MilestoneViewModel milestone) throws LedgerExc
257259
try {
258260
Hash tail = transactionViewModel.getHash();
259261
Map<Hash, Long> balanceChanges = generateBalanceDiff(new HashSet<>(), tail,
260-
snapshotProvider.getLatestSnapshot().getIndex());
262+
snapshotProvider.getLatestSnapshot().getIndex(), false);
261263
successfullyProcessed = balanceChanges != null;
262264
if (successfullyProcessed) {
263265
successfullyProcessed = snapshotProvider.getLatestSnapshot().patchedState(

src/main/java/com/iota/iri/service/milestone/impl/MilestoneServiceImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ public MilestoneValidity validateMilestone(TransactionViewModel transactionViewM
162162
return existingMilestone.getHash().equals(transactionViewModel.getHash()) ? VALID : INVALID;
163163
}
164164

165-
final List<TransactionViewModel> bundleTransactions = bundleValidator.validate(tangle,
165+
final List<TransactionViewModel> bundleTransactions = bundleValidator.validate(tangle, true,
166166
snapshotProvider.getInitialSnapshot(), transactionViewModel.getHash());
167167

168168
if (bundleTransactions.isEmpty()) {

src/main/java/com/iota/iri/service/spentaddresses/impl/SpentAddressesServiceImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ private boolean wasTransactionSpentFrom(TransactionViewModel tx) throws Exceptio
122122

123123
private boolean isBundleValid(Hash tailHash) throws Exception {
124124
List<TransactionViewModel> validation =
125-
bundleValidator.validate(tangle, snapshotProvider.getInitialSnapshot(), tailHash);
125+
bundleValidator.validate(tangle, false, snapshotProvider.getInitialSnapshot(), tailHash);
126126
return (CollectionUtils.isNotEmpty(validation) && validation.get(0).getValidity() == 1);
127127
}
128128

0 commit comments

Comments
 (0)