-
Notifications
You must be signed in to change notification settings - Fork 370
Add transaction solidifier #1805
Add transaction solidifier #1805
Conversation
private TransactionSolidifier txSolidifier; | ||
private TipsViewModel tipsViewModel; | ||
private Tangle tangle; | ||
private TransactionViewModel tip; |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
@VisibleForTesting | ||
void injectTip(TransactionViewModel tvm) throws Exception { | ||
tip = tvm; | ||
tip.updateSolid(true); | ||
} |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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()); |
There was a problem hiding this comment.
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. | ||
**/ |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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...
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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?
if(!transactionsToSolidify.contains(hash)) { | ||
if (transactionsToSolidify.size() >= MAX_SIZE - 1) { | ||
transactionsToSolidify.remove(); | ||
} | ||
|
||
transactionsToSolidify.put(hash); |
There was a problem hiding this comment.
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
...
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
if(maxProcessedTransactions != Integer.MAX_VALUE) { | ||
maxProcessedTransactions += analyzedHashes.size(); | ||
} |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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)); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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?
…lC/iri into add-transaction-solidifier
@DyrellC added 9 more commits since @GalRogozinski requested review. I will wait for @DyrellC to request review again though a comment |
There was a problem hiding this 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); |
There was a problem hiding this comment.
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 :-)
if(maxProcessedTransactions != Integer.MAX_VALUE) { | ||
maxProcessedTransactions += analyzedHashes.size(); | ||
} |
There was a problem hiding this comment.
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)); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
There was a problem hiding this 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 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 👍
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 thecheckSolidity
call within theTransactionValidator
, as is done in theMilestoneSolidifier
, 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 tocheckConsistency
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
How Has This Been Tested?
Checklist: