Skip to content

Commit

Permalink
Transaction Validator Documentation (iotaledger#1142)
Browse files Browse the repository at this point in the history
* Transaction Validator docs

* Update src/main/java/com/iota/iri/TransactionValidator.java

Co-Authored-By: GalRogozinski <galrogogit@gmail.com>

* Update src/main/java/com/iota/iri/TransactionValidator.java

Co-Authored-By: GalRogozinski <galrogogit@gmail.com>

* Update src/main/java/com/iota/iri/TransactionValidator.java

Co-Authored-By: GalRogozinski <galrogogit@gmail.com>

* Update src/main/java/com/iota/iri/TransactionValidator.java

Co-Authored-By: GalRogozinski <galrogogit@gmail.com>

* Update src/main/java/com/iota/iri/TransactionValidator.java

Co-Authored-By: GalRogozinski <galrogogit@gmail.com>

* Update src/main/java/com/iota/iri/TransactionValidator.java

Co-Authored-By: GalRogozinski <galrogogit@gmail.com>

* Update src/main/java/com/iota/iri/TransactionValidator.java

Co-Authored-By: GalRogozinski <galrogogit@gmail.com>

* Update src/main/java/com/iota/iri/TransactionValidator.java

Co-Authored-By: GalRogozinski <galrogogit@gmail.com>

* Update src/main/java/com/iota/iri/TransactionValidator.java

Co-Authored-By: GalRogozinski <galrogogit@gmail.com>

* Update src/main/java/com/iota/iri/TransactionValidator.java

Co-Authored-By: GalRogozinski <galrogogit@gmail.com>

* Update src/main/java/com/iota/iri/TransactionValidator.java

Co-Authored-By: GalRogozinski <galrogogit@gmail.com>

* Update src/main/java/com/iota/iri/TransactionValidator.java

Co-Authored-By: GalRogozinski <galrogogit@gmail.com>

* Update src/main/java/com/iota/iri/TransactionValidator.java

Co-Authored-By: GalRogozinski <galrogogit@gmail.com>

* Update src/main/java/com/iota/iri/TransactionValidator.java

Co-Authored-By: GalRogozinski <galrogogit@gmail.com>

* Update src/main/java/com/iota/iri/TransactionValidator.java

Co-Authored-By: GalRogozinski <galrogogit@gmail.com>

* Update src/main/java/com/iota/iri/TransactionValidator.java

* update ledger validator docs

* Update src/main/java/com/iota/iri/TransactionValidator.java

Co-Authored-By: GalRogozinski <galrogogit@gmail.com>

* Update src/main/java/com/iota/iri/TransactionValidator.java

Co-Authored-By: GalRogozinski <galrogogit@gmail.com>

* Update src/main/java/com/iota/iri/TransactionValidator.java

Co-Authored-By: GalRogozinski <galrogogit@gmail.com>

* Update src/main/java/com/iota/iri/TransactionValidator.java

Co-Authored-By: GalRogozinski <galrogogit@gmail.com>

* Update src/main/java/com/iota/iri/TransactionValidator.java

Co-Authored-By: GalRogozinski <galrogogit@gmail.com>

* Update src/main/java/com/iota/iri/TransactionValidator.java

Co-Authored-By: GalRogozinski <galrogogit@gmail.com>

* fix Transaction Validator documentation
  • Loading branch information
GalRogozinski authored and DyrellC committed Nov 16, 2018
1 parent f38a0eb commit 938ac4c
Showing 1 changed file with 156 additions and 4 deletions.
160 changes: 156 additions & 4 deletions src/main/java/com/iota/iri/TransactionValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@

public class TransactionValidator {
private static final Logger log = LoggerFactory.getLogger(TransactionValidator.class);
private static final int TESTNET_MWM_CAP = 13;
public static final int SOLID_SLEEP_TIME = 500;

private final Tangle tangle;
private final TipsViewModel tipsViewModel;
private final TransactionRequester transactionRequester;
Expand All @@ -29,14 +32,34 @@ public class TransactionValidator {
private static final long MAX_TIMESTAMP_FUTURE = 2L * 60L * 60L;
private static final long MAX_TIMESTAMP_FUTURE_MS = MAX_TIMESTAMP_FUTURE * 1_000L;


/////////////////////////////////fields for solidification thread//////////////////////////////////////

private Thread newSolidThread;

/**
* If true use {@link #newSolidTransactionsOne} while solidifying. Else use {@link #newSolidTransactionsTwo}.
*/
private final AtomicBoolean useFirst = new AtomicBoolean(true);
/**
* Is {@link #newSolidThread} shutting down
*/
private final AtomicBoolean shuttingDown = new AtomicBoolean(false);
/**
* mutex for solidification
*/
private final Object cascadeSync = new Object();
private final Set<Hash> newSolidTransactionsOne = new LinkedHashSet<>();
private final Set<Hash> newSolidTransactionsTwo = new LinkedHashSet<>();

/**
* Constructor for Tangle Validator
*
* @param tangle relays tangle data to and from the persistence layer
* @param tipsViewModel container that gets updated with the latest tips (transactions with no children)
* @param transactionRequester used to request missing transactions from neighbors
* @param config configuration for obtaining snapshot data
*/
TransactionValidator(Tangle tangle, TipsViewModel tipsViewModel, TransactionRequester transactionRequester,
SnapshotConfig config) {
this.tangle = tangle;
Expand All @@ -46,40 +69,96 @@ public class TransactionValidator {
this.snapshotTimestampMs = snapshotTimestamp * 1000;
}

/**
* Does two things:
* <ol>
* <li>Sets the minimum weight magnitude (MWM). POW on a transaction is validated by counting a certain
* number of consecutive 9s in the end of the transaction hash. The number of 9s is the MWM.</li>
* <li>Starts the transaction solidification thread.</li>
* </ol>
*
*
* @see #spawnSolidTransactionsPropagation()
* @param testnet <tt>true</tt> if we are in testnet mode, this caps {@code mwm} to {@value #TESTNET_MWM_CAP}
* regardless of parameter input.
* @param mwm minimum weight magnitude: the minimal number of 9s that ought to appear at the end of the transaction
* hash
*/
public void init(boolean testnet, int mwm) {
setMwm(testnet, mwm);

newSolidThread = new Thread(spawnSolidTransactionsPropagation(), "Solid TX cascader");
newSolidThread.start();
}

//Package Private For Testing
void setMwm(boolean testnet, int mwm) {
minWeightMagnitude = mwm;

//lowest allowed MWM encoded in 46 bytes.
if (!testnet){
minWeightMagnitude = Math.max(minWeightMagnitude, 13);
minWeightMagnitude = Math.max(minWeightMagnitude, TESTNET_MWM_CAP);
}
}

/**
* Shutdown roots to tip solidification thread
* @throws InterruptedException
* @see #spawnSolidTransactionsPropagation()
*/
public void shutdown() throws InterruptedException {
shuttingDown.set(true);
newSolidThread.join();
}

/**
* @return the minimal number of trailing 9s that have to be present at the end of the transaction hash
* in order to validate that sufficient proof of work has been done
*/
public int getMinWeightMagnitude() {
return minWeightMagnitude;
}

/**
* Checks that the timestamp of the transaction is below the last global snapshot time
* or more than {@value #MAX_TIMESTAMP_FUTURE} seconds in the future, and thus invalid.
*
* <p>
* First the attachment timestamp (set after performing POW) is checked, and if not available
* the regular timestamp is checked. Genesis transaction will always be valid.
* </p>
* @param transactionViewModel transaction under test
* @return <tt>true</tt> if timestamp is not in valid bounds and {@code transactionViewModel} is not genesis.
* Else returns <tt>false</tt>.
*/
private boolean hasInvalidTimestamp(TransactionViewModel transactionViewModel) {
if (transactionViewModel.getAttachmentTimestamp() == 0) {
return transactionViewModel.getTimestamp() < snapshotTimestamp && !Objects.equals(transactionViewModel.getHash(), Hash.NULL_HASH)
return transactionViewModel.getTimestamp() < snapshotTimestamp
//you are valid if you are the genesis
&& !Objects.equals(transactionViewModel.getHash(), Hash.NULL_HASH)
|| transactionViewModel.getTimestamp() > (System.currentTimeMillis() / 1000) + MAX_TIMESTAMP_FUTURE;
}
return transactionViewModel.getAttachmentTimestamp() < snapshotTimestampMs
|| transactionViewModel.getAttachmentTimestamp() > System.currentTimeMillis() + MAX_TIMESTAMP_FUTURE_MS;
}

/**
* Runs the following validation checks on a transaction:
* <ol>
* <li>{@link #hasInvalidTimestamp} check.</li>
* <li>Check that no value trits are set beyond the usable index, otherwise we will have values larger
* than max supply.</li>
* <li>Check that sufficient POW was performed.</li>
* <li>In value transactions, we check that the address has 0 set as the last trit. This must be because of the
* conversion between bytes to trits.</li>
* </ol>
*Exception is thrown upon failure.
*
* @param transactionViewModel transaction that should be validated
* @param minWeightMagnitude the minimal number of trailing 9s at the end of the transaction hash
* @throws StaleTimestampException if timestamp check fails
* @throws IllegalStateException if any of the other checks fail
*/
public void runValidation(TransactionViewModel transactionViewModel, final int minWeightMagnitude) {
transactionViewModel.setMetadata();
transactionViewModel.setAttachmentData();
Expand All @@ -102,12 +181,28 @@ public void runValidation(TransactionViewModel transactionViewModel, final int m
}
}

/**
* Creates a new transaction from {@code trits} and validates it with {@link #runValidation}.
*
* @param trits raw transaction trits
* @param minWeightMagnitude minimal number of trailing 9s in transaction for POW validation
* @return the transaction resulting from the raw trits if valid.
* @throws RuntimeException if validation fails
*/
public TransactionViewModel validateTrits(final byte[] trits, int minWeightMagnitude) {
TransactionViewModel transactionViewModel = new TransactionViewModel(trits, TransactionHash.calculate(trits, 0, trits.length, SpongeFactory.create(SpongeFactory.Mode.CURLP81)));
runValidation(transactionViewModel, minWeightMagnitude);
return transactionViewModel;
}

/**
* Creates a new transaction from {@code bytes} and validates it with {@link #runValidation}.
*
* @param bytes raw transaction bytes
* @param minWeightMagnitude minimal number of trailing 9s in transaction for POW validation
* @return the transaction resulting from the raw bytes if valid
* @throws RuntimeException if validation fails
*/
public TransactionViewModel validateBytes(final byte[] bytes, int minWeightMagnitude, Sponge curl) {
TransactionViewModel transactionViewModel = new TransactionViewModel(bytes, TransactionHash.calculate(bytes, TRINARY_SIZE, curl));
runValidation(transactionViewModel, minWeightMagnitude);
Expand Down Expand Up @@ -197,12 +292,16 @@ public void addSolidTransaction(Hash hash) {
}
}

/**
* Creates a runnable that runs {@link #propagateSolidTransactions()} in a loop every {@value #SOLID_SLEEP_TIME} ms
* @return runnable that is not started
*/
private Runnable spawnSolidTransactionsPropagation() {
return () -> {
while(!shuttingDown.get()) {
propagateSolidTransactions();
try {
Thread.sleep(500);
Thread.sleep(SOLID_SLEEP_TIME);
} catch (InterruptedException e) {
// Ignoring InterruptedException. Do not use Thread.currentThread().interrupt() here.
log.error("Thread was interrupted: ", e);
Expand All @@ -211,11 +310,18 @@ private Runnable spawnSolidTransactionsPropagation() {
};
}

/**
* Iterates over all currently known solid transactions. For each solid transaction, we find
* its children (approvers) and try to quickly solidify them with {@link #quietQuickSetSolid}.
* If we manage to solidify the transactions, we add them to the solidification queue for a traversal by a later run.
*/
//Package private for testing
void propagateSolidTransactions() {
Set<Hash> newSolidHashes = new HashSet<>();
useFirst.set(!useFirst.get());
//synchronized to make sure no one is changing the newSolidTransactions collections during addAll
synchronized (cascadeSync) {
//We are using a collection that doesn't get updated by other threads
if (useFirst.get()) {
newSolidHashes.addAll(newSolidTransactionsTwo);
newSolidTransactionsTwo.clear();
Expand Down Expand Up @@ -244,6 +350,31 @@ void propagateSolidTransactions() {
}
}


/**
* Updates a transaction after it was stored in the tangle. Tells the node to not request the transaction anymore,
* to update the live tips accordingly, and attempts to quickly solidify the transaction.
*
* <p/>
* Performs the following operations:
*
* <ol>
* <li>Removes {@code transactionViewModel}'s hash from the the request queue since we already found it.</li>
* <li>If {@code transactionViewModel} has no children (approvers), we add it to the node's active tip list.</li>
* <li>Removes {@code transactionViewModel}'s parents (branch & trunk) from the node's tip list
* (if they're present there).</li>
* <li>Attempts to quickly solidify {@code transactionViewModel} by checking whether its direct parents
* are solid. If solid we add it to the queue transaction solidification thread to help it propagate the
* solidification to the approving child transactions.</li>
* <li>Requests missing direct parent (trunk & branch) transactions that are needed to solidify
* {@code transactionViewModel}.</li>
* </ol>
* @param transactionViewModel received transaction that is being updated
* @throws Exception if an error occurred while trying to solidify
* @see TipsViewModel
*/
//Not part of the validation process. This should be moved to a component in charge of
//what transaction we gossip.
public void updateStatus(TransactionViewModel transactionViewModel) throws Exception {
transactionRequester.clearTransactionRequest(transactionViewModel.getHash());
if(transactionViewModel.getApprovers(tangle).size() == 0) {
Expand All @@ -259,6 +390,11 @@ public void updateStatus(TransactionViewModel transactionViewModel) throws Excep
}
}

/**
* Perform a {@link #quickSetSolid} while capturing and logging errors
* @param transactionViewModel transaction we try to solidify.
* @return <tt>true</tt> if we managed to solidify, else <tt>false</tt>.
*/
private boolean quietQuickSetSolid(TransactionViewModel transactionViewModel) {
try {
return quickSetSolid(transactionViewModel);
Expand All @@ -268,6 +404,13 @@ private boolean quietQuickSetSolid(TransactionViewModel transactionViewModel) {
}
}

/**
* Tries to solidify the transactions quickly by performing {@link #checkApproovee} on both parents (trunk and
* branch). If the parents are solid, mark the transactions as solid.
* @param transactionViewModel transaction to solidify
* @return <tt>true</tt> if we made the transaction solid, else <tt>false</tt>.
* @throws Exception
*/
private boolean quickSetSolid(final TransactionViewModel transactionViewModel) throws Exception {
if(!transactionViewModel.isSolid()) {
boolean solid = true;
Expand All @@ -286,6 +429,12 @@ private boolean quickSetSolid(final TransactionViewModel transactionViewModel) t
return false;
}

/**
* If the the {@code approvee} is missing, request it from a neighbor.
* @param approovee transaction we check.
* @return true if {@code approvee} is solid.
* @throws Exception if we encounter an error while requesting a transaction
*/
private boolean checkApproovee(TransactionViewModel approovee) throws Exception {
if(approovee.getType() == PREFILLED_SLOT) {
transactionRequester.requestTransaction(approovee.getHash(), false);
Expand All @@ -297,11 +446,14 @@ private boolean checkApproovee(TransactionViewModel approovee) throws Exception
return approovee.isSolid();
}

//for testing
//Package Private For Testing
boolean isNewSolidTxSetsEmpty () {
return newSolidTransactionsOne.isEmpty() && newSolidTransactionsTwo.isEmpty();
}

/**
* Thrown if transaction fails {@link #hasInvalidTimestamp} check.
*/
public static class StaleTimestampException extends RuntimeException {
StaleTimestampException (String message) {
super(message);
Expand Down

0 comments on commit 938ac4c

Please sign in to comment.