After POW completion, a nonce is generated and added to the transaction object.
- How does the receiving full node verify it?
- What will happen if a corrupted node updates the wrong nonce without doing the POW?
The idea behind PoW is that it is easy to compute a hash of a transaction, but hard to find a transaction where the hash has certain properties. IOTA uses Curl hash function for this, and the Proof of Work requires that the hash ends with a certain number of zero trits (or when written as trytes, a certain number of nines). This number of zeros (also known as the Minimum Weight Magnitude, MWM) is currently 14 on mainnet and 9 on testnet.
This is easy to verify (compute one hash), but to find one, the entity that performs PoW has to guess nonces until it finds one that satisfies this condition (so it has to compute in average 3^14
(about 2.3 million) hashes).
Whenever a transaction is sent to a node (being it from a light wallet or from another node), the receiving node has to calculate the transaction hash (used for bookkeeping and later retrieving the transaction again). After calculating the hash, the node counts the number of zero trits at the end. In case there are less than MWM zeros at the end, the transaction is rejected and not stored on the node.
In case the transaction was received from a neighbor node, the "invalid transaction received" counter of that neighbor is increased, so a misbehaving or malicious node (which sends lots of invalid transactions) will appear when the node operator reviews his neighbor statistics.
3^14
.)
When a node receive a transaction it validates it with code bellow.
First, it calculates the transaction hash with this code :
//bytes are "transaction bytes" and contains the nonce
Hash.calculate(bytes, TransactionViewModel.TRINARY_SIZE, curl);
Then the transaction is validated against 4 criteria with this code:
(abstract in plain text)
.
private static void runValidation(TransactionViewModel transactionViewModel, final int minWeightMagnitude) {
transactionViewModel.setMetadata();
if(transactionViewModel.getTimestamp() < 1508760000 && !transactionViewModel.getHash().equals(Hash.NULL_HASH)) {
throw new RuntimeException("Invalid transaction timestamp.");
}
for (int i = VALUE_TRINARY_OFFSET + VALUE_USABLE_TRINARY_SIZE; i < VALUE_TRINARY_OFFSET + VALUE_TRINARY_SIZE; i++) {
if (transactionViewModel.trits()[i] != 0) {
throw new RuntimeException("Invalid transaction value");
}
}
int weightMagnitude = transactionViewModel.weightMagnitude;
if(weightMagnitude < minWeightMagnitude) {
throw new RuntimeException("Invalid transaction hash");
}
if (transactionViewModel.value() != 0 && transactionViewModel.getAddressHash().trits()[Curl.HASH_LENGTH - 1] != 0) {
throw new RuntimeException("Invalid transaction address");
}
}