3

In the IRI Node class, this code segment checks to see if the requested transaction hash is the same as the received transaction hash:

https://github.com/iotaledger/iri/blob/dev/src/main/java/com/iota/iri/network/Node.java#L284

//Request bytes    
//add request to reply queue (requestedHash, neighbor)
Hash requestedHash = new Hash(receivedData, TransactionViewModel.SIZE, reqHashSize);
if (requestedHash.equals(receivedTransactionHash)) {
     //requesting a random tip
     requestedHash = Hash.NULL_HASH;
}

How is it possible for the request hash to be part of the received transaction hash since the received transaction hash is not known until the transaction is made?

Perhaps I am missing some detail - can someone please explain the purpose of this.


History of This Code Segment

After the first submitted answer below, and after Come_From_Beyond made some comments, I did some digging to see the commit history of this code.

According to the earliest available commits for the Node class, when the transaction hash and the requested hash were the same, the request was to be the Milestone that is requested. Perhaps someone can shed some light on why this existed like this and why it was changed.

Oct 24, 2016 - If the requested hash is same, then return a Milestone https://github.com/iotaledger/iri/blob/5db789222fabd11a9880b457b2b9f24cd783789f/src/iri/Node.java#L90

if (Arrays.equals(requestedTransaction, receivedTransaction.hash)) {
    transactionPointer = Storage.transactionPointer(Milestone.latestMilestone.bytes);
} else {
    transactionPointer = Storage.transactionPointer(requestedTransaction);
}

Dec 23, 2016 - Introduces Concept of 'Experimental Random Tip Broadcaster' https://github.com/iotaledger/iri/blob/577f2ad2db561a4044cbf9a81beae15da599d2ab/src/main/java/com/iota/iri/service/Node.java#L201

if (Arrays.equals(requestedTransaction, receivedTransaction.hash)) {
    if (Configuration.booling(DefaultConfSettings.EXPERIMENTAL) &&
        ++randomTipBroadcastCounter % 3 == 0) {
        // see code re "Experimental: Random Tip Broadcaster"
    } else {
        transactionPointer = StorageTransactions.instance()
            .transactionPointer(Milestone.latestMilestone.bytes());
    }
} else {
    transactionPointer = StorageTransactions.instance().transactionPointer(requestedTransaction);
}

Jan 29, 2017 - Compare changed to NULL HASH and other changes to scheme https://github.com/iotaledger/iri/blob/61ad77be4ce01ee1cc8530320f7c234ee660c822/src/main/java/com/iota/iri/service/Node.java#L205

(truncated for brevity)

if (Arrays.equals(requestedTransaction, Hash.NULL_HASH.bytes())) {
    if (randomTipBroadcastCounter % 9 == 0) {
        // see code "Random Broadcaster - latestMilestone index==0."
    }
    else if (randomTipBroadcastCounter % 6 == 0) {
        // see code re Milestone
    }
    else if (randomTipBroadcastCounter % 3 == 0) {
        // see code "Random Broadcaster - random tip."
    }
} else {
    transactionPointer = StorageTransactions.instance().transactionPointer(requestedTransaction);
}

The Role of P_MILESTONE TO SELECT and the TransactionRequester

The actual actions of the transaction to request are further obfuscated by the last minute code block before transmission in Node.

See https://github.com/iotaledger/iri/blob/dev/src/main/java/com/iota/iri/network/Node.java#L483

synchronized (sendingPacket) {
    System.arraycopy(transactionViewModel.getBytes(), 0, sendingPacket.getData(), 0, TransactionViewModel.SIZE);
    Hash hash = transactionRequester.transactionToRequest(rnd.nextDouble() < P_SELECT_MILESTONE);
    System.arraycopy(hash != null ? hash.bytes() : transactionViewModel.getHash().bytes(), 0,
            sendingPacket.getData(), TransactionViewModel.SIZE, reqHashSize);
    neighbor.send(sendingPacket);
}

By default, P_MILESTONE_TO_SELECT is set to 70%, and this tells the transactionRequester to use a Milestone if any are found in the milestoneTransactionsToRequest queue.

See https://github.com/iotaledger/iri/blob/dev/src/main/java/com/iota/iri/network/TransactionRequester.java#L90

This further obfuscates the actions of a request, although a little investigation will find that during validation, if any trunk or branch is found to be a Milestone, then it is added to transactionsToRequest. And also, if the LedgerValidator thread also finds any missing Milestones, they too are added to the transactionsToRequest queue.

This complicates the logic of a requested transaction quite a bit as it turns out that a requested transaction of either (1) an identical hash or (2) NULL_HASH, has the effect of not //requesting a random tip as the comments in Node imply, but of an elaborate process of preferring a Milestone if one is missing from the system.

From what I can tell of the effect of the LedgerValidator, all tips are checked during the routine Milestone.java checking thread Solid Milestone Tracker and when the LedgerValidator is called, missing Milestones are inventoried and queued to receive priority treatment within the system.

I suppose this begs the question ...

How often is a Milestone missing from the system? That determines, ultimately, what a requested transaction actually requests.

2

Broadcast a tx/Request a missing tx

When IRI broadcast a transaction to neighbors, it can append to the transaction data the hash of a transaction to request (i.e. the hash of a transaction unknown by the broadcaster), or the NULL_HASH if the broadcaster don't have any transaction to request.

Basically, when IRI broadcast a tx, the packet will look like this :

<SLOT_0[1604 bytes]: full details of broadcasted tx><SLOT_1[46 bytes]: hash of a missing tx>

When IRI receive a transaction from neighbors, it process the transaction and look at the appended hash. If the broadcaster append the hash of the broadcasted transaction: it will respond with a random tip. If the broadcaster append a particular hash, it will respond with the requested transaction (when possible). That's the way to request a missing transaction.


For anyone interested in more details here is the code (for the send and receive):

[Sending]

Here is the code to broadcast a transaction and you will notice that if the transactionRequester don't give any hash : the hash of the broadcasted transaction is used.

Hash hash = transactionRequester.transactionToRequest(rnd.nextDouble() < P_SELECT_MILESTONE);
System.arraycopy(hash != null ? hash.bytes() : transactionViewModel.getHash().bytes(), 0,
                    sendingPacket.getData(), TransactionViewModel.SIZE, reqHashSize);  

[Receiving (and responding)]

And here is the when receiving a transaction :

if (requestedHash.equals(receivedTransactionHash)) {
    //requesting a random tip
    requestedHash = Hash.NULL_HASH;
}

addReceivedDataToReplyQueue(requestedHash, neighbor);

And when handeling the replyQueue :

if (requestedHash.equals(Hash.NULL_HASH)) {
        //Random Tip Request
        try {
            if (transactionRequester.numberOfTransactionsToRequest() > 0 && rnd.nextDouble() < P_REPLY_RANDOM_TIP) {
                neighbor.incRandomTransactionRequests();
                transactionPointer = getRandomTipPointer();
                transactionViewModel = TransactionViewModel.fromHash(tangle, transactionPointer);
            } else {
                //no tx to request, so no random tip will be sent as a reply.
                return;
            }
        } catch (Exception e) {
            log.error("Error getting random tip.", e);
        }
    } else {
        //find requested trytes
        try {
            //transactionViewModel = TransactionViewModel.find(Arrays.copyOf(requestedHash.bytes(), TransactionRequester.REQUEST_HASH_SIZE));
            transactionViewModel = TransactionViewModel.fromHash(tangle, new Hash(requestedHash.bytes(), 0, reqHashSize));
            //log.debug("Requested Hash: " + requestedHash + " \nFound: " + transactionViewModel.getHash());
        } catch (Exception e) {
            log.error("Error while searching for transaction.", e);
        }
    }
  • 1
    Old versions of IRI used <Details of tx A><Hash of tx A> to signal "Send me a tip, any tip you have". – Come-from-Beyond Jul 30 '18 at 7:01
  • @AustinPowers as usual: the hash of a missing tx or the null hash. – ben75 Jul 30 '18 at 8:04
  • @AustinPowers it appears that IRI is still using <Details of tx A><Hash of tx A> to signal "Send me a tip, any tip you have" in the current implementation. I fixed my post. – ben75 Jul 30 '18 at 15:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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