3

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?

2 Answers 2

4

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.

4
  • Is this also done by nodes when they accept sync' data from other nodes, to ensure the node was honest and didn't accept an invalid nonce without bothering to check? Commented Feb 1, 2018 at 19:19
  • 1
    There is no distinction between "syncing" and normal "receiving transactions from another node" (A node will treat incoming transactions the same, regardless whether it requested them first or they were sent to it voluntarily), so yes.
    – mihi
    Commented Feb 1, 2018 at 21:42
  • Why 3^14 / 2 and not 3^14? @mihi If the output is casual I have 3^243 possible outputs. Only 3^(243-14) are valid for the PoW (last 14 must be 0 trits). This means that the probability to get it right is 3^229 / 3^243 = 1 / 3^14. Now, this has geometric distribution with parameter p = 1 / 3^14, which means the the mean value is 1/p = 3^14. What am I getting wrong? Commented Jun 11, 2018 at 13:07
  • Thanks @DiegoStucchi your math is right. I fell in the old fallacy that it takes 3 tries in average until you roll a six on a six-sided di(c)e, but the correct answer is 6. (The last trits are independent from the first ones so you can ignore all but the last trits. And the number of tries you need until you "roll" all zero trits is 3^14.)
    – mihi
    Commented Jun 11, 2018 at 19:20
1

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)

  1. Transaction timestamp shouldn't be to old (after 10/23/17 12:00 PM) (or if it is transaction hash should be null)
  2. Transaction value should be encoded correctly (a precise range of trits should always be 0)
  3. weightMagnitude (i.e. number of trailing zeros in transaction hash) is at least a threshold (14 on the current main net). That's here that it check the POW.
  4. for non-zero transaction, the the last trit of the address hash shouldn't be 0 (I don't know the reason...)

.

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");
    }
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.