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

Add transaction solidifier #1805

Merged
merged 22 commits into from
Apr 2, 2020

Conversation

DyrellC
Copy link
Contributor

@DyrellC DyrellC commented Mar 19, 2020

Description

This contains the bulk of changes from #1646 and relies on #1804. Transaction solidification should not be contained within the TransactionValidator and depending on the number of transactions required for a walk, using the checkSolidity call within the TransactionValidator, as is done in the MilestoneSolidifier, can take a significant portion of time. In order to mitigate this, the solidification and propagation logic has been placed into its own class running in its own thread independent of the validation logic. Instead of making direct calls to checkConsistency a queue is formed so as to not lock up any other threads, and the solidity of a transaction will first be checked via the view model before it is placed into the queue.

This is a step towards introducing the milestone pipeline refactoring.

Type of change

  • Enhancement (a non-breaking change which adds functionality)

How Has This Been Tested?

  • All corresponding tests pass: Regression, Unit and Synchronisation

Checklist:

  • My code follows the style guidelines for this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

private TransactionSolidifier txSolidifier;
private TipsViewModel tipsViewModel;
private Tangle tangle;
private TransactionViewModel tip;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like this field...
It violates Domain Driven Design principles

I think you can do the same logic w/o this tip field

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was there for the unit test but if you want it removed i'll have to revisit the test to inject a tip into the process.

Comment on lines 92 to 96
@VisibleForTesting
void injectTip(TransactionViewModel tvm) throws Exception {
tip = tvm;
tip.updateSolid(true);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you do my change you can remove this ugliness

@@ -184,6 +199,7 @@ private void addStage(String name, BlockingQueue<ProcessingContext> queue,
public void process(Neighbor neighbor, ByteBuffer data) {
try {
preProcessStageQueue.put(new ProcessingContext(new PreProcessPayload(neighbor, data)));
refillBroadcastQueue();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's kinda hacky...
It is unexpected that when you start processing data you also do something else

I would have expected that this will purely happen in the solidification stage
Maybe it is because you are in the middle of the design?

}
txSolidifier.clearFromBroadcastQueue(toRemove);
} catch(InterruptedException e){
log.info(e.getMessage());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thread.currentThread().interrupt();

* @param trytes Transaction data to be stored.
* @return {@link com.iota.iri.service.dto.AbstractResponse.Emptyness}
* @throws Exception When storing or updating a transaction fails.
**/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know it happened automatically
hopefully we will have time in some sprint to breath and fix all these formatting issues that we have

next time try to refrain

@@ -66,33 +67,33 @@
public class API {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe try to re commit this class w/o all the formatting 😅

* A queue for processing transactions with the {@link #checkSolidity(Hash)} call. Once a transaction has been
* marked solid it will be placed into the {@link #transactionsToBroadcast} queue.
*/
private BlockingQueue<Hash> transactionsToSolidify = new ArrayBlockingQueue<>(MAX_SIZE);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I remember I told you to use ArrayBlockingQueue...
We have a bug in networking #1742, that is fixed by doing this GalRogozinski@f65e29b

What happens is that sometiems the queue in networking gets filled in high tps regimes...
So I am rethinking my recommendation

Because before a memory attack IRI just might stop functioning...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I always wondered about that but wasn't sure if i was thinking straight about it. Should I switch this to a LinkedBlockingQueue instead?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes

* @param snapshotProvider For fetching entry points for solidity checks
* @param transactionRequester A requester for missing transactions
*/
public TransactionSolidifierImpl(Tangle tangle, SnapshotProvider snapshotProvider, TransactionRequester transactionRequester,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe have an inner class for the propagation solidifier?
just to separate between responsibilities?

Comment on lines 105 to 110
if(!transactionsToSolidify.contains(hash)) {
if (transactionsToSolidify.size() >= MAX_SIZE - 1) {
transactionsToSolidify.remove();
}

transactionsToSolidify.put(hash);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you know I see no reason why to have TransactionSolidifier should run on a separate thread then milestone solidifier

This logic just risks currently more bug entering, and I can't think on why you may improve performance here.
Why don't you have the milestoneSolidifier just call checkSollidity like before...
I thought you were just going to move logic to the transactionSolidifier...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was logic that was there for preparation for the milestone epic. I can have it run the way you describe for now and we can re-approach the topic when the epic is ready. The motivation here was to create the separation of powers between state checking milestones, and solidifying transactions. In some instances when checkSolidity() is called, it takes a while to run through all the transactions for analysing. This is especially true during larger solidification gaps with a large number of transactions present between milestones. When these calls hang, it slows down the milestoneSolidifer because that thread is dependent on the checkSolidity() call finishing to continue processing the available transactions in the queue. That includes adding newly seen milestones to the queue.

I've seen numerous instances during longer syncs, where there will be no message from the solidifier for an extended period of time. When debugging into the nodes to see why that is, I found that the checkSolidity() call was taking a long time to process through these walks and would finish after processing nearly the maximum number of transactions (50000) while doing so. Sometimes these calls could take anywhere between 5 and 20 seconds to process through. Since the queue for the milestone solidifier holds 20 transactions to be put through this call sequentially, there can be long gaps between the solidifier processing new milestones that may have been received through the requests and gossip. The difference is nearly neglible when doing smaller syncs, but over longer periods of time (say a few days for example) it can start to become more noticeable in the later parts of the synchronisation.

So the logic here was to make sure that the milestoneSolidifier is focused on seeing and processing the milestones with minimal interruption, while the transactionSolidifier would asynchronously be managing the actual solidification process.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd put this in a separate PR then
This is not too many lines of code, but still a big important change

Comment on lines 188 to 190
if(maxProcessedTransactions != Integer.MAX_VALUE) {
maxProcessedTransactions += analyzedHashes.size();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know you copy pasted but this line of code doesn't make sense and you are now rewriting

maybe use Math.addExact and remove the funny if

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess you want to do this in another PR?

maxProcessedTransactions += analyzedHashes.size();
}
boolean solid = true;
final Queue<Hash> nonAnalyzedTransactions = new LinkedList<>(Collections.singleton(hash));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably an array deque will be faster

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess you want to do this in another PR?

void propagateSolidTransactions() {
while(!Thread.currentThread().isInterrupted() && solidTransactions.peek() != null) {
try {
Hash hash = solidTransactions.poll();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a theory on the solidification problem...
I think we are trying to solidify before we go through the receive stage

So we fail to getApprovers because they were not received yet stored
And when they come in, the non received transaction is polled out already...

Makes sense?

@kwek20
Copy link
Contributor

kwek20 commented Mar 27, 2020

@DyrellC added 9 more commits since @GalRogozinski requested review. I will wait for @DyrellC to request review again though a comment

Copy link
Contributor

@GalRogozinski GalRogozinski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Almost there and looking good :-)

@@ -56,6 +54,8 @@ public ProcessingContext process(ProcessingContext ctx){
TransactionViewModel tvm = payload.getTransaction();

if (tvm.isSolid() || txSolidifier.quickSetSolid(tvm)) {
// If the transaction is in the solidifier broadcast queue, remove it as it will be broadcast now
txSolidifier.clearFromBroadcastQueue(tvm);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm... I think this is a good defense :-)

Comment on lines 188 to 190
if(maxProcessedTransactions != Integer.MAX_VALUE) {
maxProcessedTransactions += analyzedHashes.size();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess you want to do this in another PR?

maxProcessedTransactions += analyzedHashes.size();
}
boolean solid = true;
final Queue<Hash> nonAnalyzedTransactions = new LinkedList<>(Collections.singleton(hash));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess you want to do this in another PR?

* A queue for processing transactions with the {@link #checkSolidity(Hash)} call. Once a transaction has been
* marked solid it will be placed into the {@link #transactionsToBroadcast} queue.
*/
private BlockingQueue<Hash> transactionsToSolidify = new ArrayBlockingQueue<>(MAX_SIZE);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes

tipsViewModel.setSolid(h);


public class TransactionPropagator {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pretty sure this can be private

@DyrellC DyrellC changed the base branch from dev to Dyrell-Milestone-Stage-Draft April 1, 2020 21:32
@DyrellC DyrellC mentioned this pull request Apr 1, 2020
6 tasks
Copy link
Contributor

@GalRogozinski GalRogozinski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Contributor

@acha-bill acha-bill left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me.
Just the one comment. @DyrellC 😆

/**
* Processes the payload of the {@link ProcessingContext} as a {@link SolidifyPayload}. First the transaction will
* be checked for solidity and validity. If the transaction is already solid or can be set solid quickly by the
* transaction validator, the transaction is passed to the {@link BroadcastStage}. If not, a random solid tip is
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can be set solid quickly by the transaction validator solidifier

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aaah nice catch thought I caught them all. Thanks 👍

@DyrellC DyrellC changed the base branch from Dyrell-Milestone-Stage-Draft to dev April 2, 2020 16:01
@GalRogozinski GalRogozinski merged commit 739ea7a into iotaledger:dev Apr 2, 2020
@acha-bill acha-bill mentioned this pull request Apr 14, 2020
6 tasks
@GalRogozinski GalRogozinski mentioned this pull request May 6, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants