-
Notifications
You must be signed in to change notification settings - Fork 25
Revisit locking design for State object #17
Comments
For what it's worth, I will note that the race condition you describe does not produce an "invalid" block in the same way that would be used in an invalid history challenge. At the leaf parsing step of the merkle sum tree, all but one of the transactions would be given a sum of 0 and so those branches would not be able to be used in an exit game at all. So, the race condition doesn't actually invalidate anything in the sense of having to exit or lose money. That said, it obviously seems like horrible practice to not solve this issue--especially if the operator is signing off on inclusion promise attestations to get the "0-conf"-like property. Another note--locking based on account does have the issue that it restricts the user from sending more than one transaction per block. Ideally, we would want to lock by range... not sure how much overhead this would end up adding. It does seem like a proper queue module could be useful here depending on how quick locking/unlocking is--from queue.js, "it can be significantly faster than using arrays." I'm not sure whether the DOS issue you described above is preventable without proper anti-DOS measures, though. On recieving a transaction, you'd add it to the queue, with a callback to include/log the transaction, and then you would just repeatedly dequeue in a loop:
|
These account locks are released upon completion of the It may be worth mentioning that I've implemented a simple promise queue & simply processed transactions in sequence but this approach seemed to have even worse memory limit issues & was slightly slower. |
Issue Status: 1. Open 2. Started 3. Submitted 4. Done This issue now has a funding of 300.0 DAI (300.0 USD @ $1.0/DAI) attached to it.
|
@karlfloersch how are you measuring the memory limit / speed issues? |
Issue Status: 1. Open 2. Started 3. Submitted 4. Done Work has been started. These users each claimed they can complete the work by 1 week, 5 days ago. 1) famiiz has applied to start work (Funders only: approve worker | reject worker). Good Learn more on the Gitcoin Issue Details page. 2) lsaether has applied to start work (Funders only: approve worker | reject worker). Would love to take a stab at this and see what kind of solution I can implement. I'm proficient in JS/ TS but can't describe a solution to this off the top of my head, would need to play around with the codebase first. Learn more on the Gitcoin Issue Details page. 3) mvayngrib has been approved to start work. i can factor out the queueing/locking logic into a separate module. You'll probably be able to get rid of your async-lock dependency too while you're at it. Learn more on the Gitcoin Issue Details page. |
is execution in order of invocation important? Currently, if the attempted lock acquisitions are something like 1. if execution in order of invocation is not important, then at every lock release, u can check the enqueued operations and choose the maximum number that can be run in parallel, as opposed to just the luck of the draw |
isnt the solution if this to add timestamp with it to solve concurrency and queue jam issue. |
This seems reasonable. It makes sense to have a datastructure which makes it easy to "choose the maximum number of parallelizable transactions" and then begin processing those transactions. The tricky part is managing this complex queue.
How does this work? Slightly confused about exactly how to use the timestamp |
@karlfloersch after playing with it on paper, and discussing with a friend, I'm no longer sure it matters whether parallelizable tasks run earlier or later. I think your locking design is actually fine efficiency-wise, the main issues being:
i see @Sprimage has been approved, but if he changes his mind, i'd like to give this a try |
Yea @mvayngrib i figured the polling loop too was an issue and decided using https://www.npmjs.com/package/promise-queue should wrap it up. If you're really interested, we can work together to come up with the best solution as soon as possible |
Ok so queuing promises can take a lot of memory. What if we move the job queue out of program memory and use something like |
@Sprimage fs sounds like a big compromise speed-wise. Blocks are processed one a time, what's the max # txs in a block? Also, as far as I understand, queueing is an exception rather than a rule. Txs can be processed in parallel and only queue up when they collide on a lock (as they are currently) |
This depends on the block time & tps as there is no hard upper limit on txs in a block. Right now the block time is set to 60 seconds, and I've benchmarked the state object alone and gotten around 3k tps on my laptop. So if we were to make the express API handle more tps, theoretically with 1 minute block times we could get blocks with 180,000 transactions, which would be approx 18MB. |
@karlfloersch are you considering using a persistent buffer of some sort, e.g. |
I know queuing is an exception. But the same is the root of this issue isn't it? |
Also external dependencies and platform specific dependencies would make |
If you have slack or something similar so we can discuss this in real time and consider the solution together would be great too |
@Sprimage Hello from Gitcoin Core - are you still working on this issue? Please submit a WIP PR or comment back within the next 3 days or you will be removed from this ticket and it will be returned to an ‘Open’ status. Please let us know if you have questions!
Funders only: Snooze warnings for 1 day | 3 days | 5 days | 10 days | 100 days |
4 similar comments
@Sprimage Hello from Gitcoin Core - are you still working on this issue? Please submit a WIP PR or comment back within the next 3 days or you will be removed from this ticket and it will be returned to an ‘Open’ status. Please let us know if you have questions!
Funders only: Snooze warnings for 1 day | 3 days | 5 days | 10 days | 100 days |
@Sprimage Hello from Gitcoin Core - are you still working on this issue? Please submit a WIP PR or comment back within the next 3 days or you will be removed from this ticket and it will be returned to an ‘Open’ status. Please let us know if you have questions!
Funders only: Snooze warnings for 1 day | 3 days | 5 days | 10 days | 100 days |
@Sprimage Hello from Gitcoin Core - are you still working on this issue? Please submit a WIP PR or comment back within the next 3 days or you will be removed from this ticket and it will be returned to an ‘Open’ status. Please let us know if you have questions!
Funders only: Snooze warnings for 1 day | 3 days | 5 days | 10 days | 100 days |
@Sprimage Hello from Gitcoin Core - are you still working on this issue? Please submit a WIP PR or comment back within the next 3 days or you will be removed from this ticket and it will be returned to an ‘Open’ status. Please let us know if you have questions!
Funders only: Snooze warnings for 1 day | 3 days | 5 days | 10 days | 100 days |
this still alive? |
Hey @mvayngrib are you interested in taking this on? I think we should be passing it back to the crowd at this point. |
@ceresstation sure, i can give it a try. I already applied though gitcoin |
Just accepted :) |
Issue Status: 1. Open 2. Started 3. Submitted 4. Done Work for 300.0 DAI (300.0 USD @ $1.0/DAI) has been submitted by: @ceresstation please take a look at the submitted work:
|
poke @ceresstation |
Issue Status: 1. Open 2. Started 3. Submitted 4. Done The funding of 300.0 DAI (300.0 USD @ $1.0/DAI) attached to this issue has been approved & issued to @mvayngrib.
|
Is your feature request related to a problem? Please describe.
The state object requires locking logic when starting a new block or ingesting transactions & deposits. An example of a race condition without locking is as follows:
tx_log
... uh oh!tx_log
now have two transactions effecting the same range in the same block! Oh no--that's invalid!I am currently solving this by locking the sending account when a transaction is added. That means that each of these txs would attempt to acquire a lock for the same sender & one would fail.
How do locks currently get acquired and released?
Right now, locking is very primitive. Lock acquiring looks like this:
state.js
Where
keywords
is every sender address in every transfer record of the transaction being added. If a lock fails to be acquired, then we wait some amount of time & try again with:Problems with this approach
I used this as a quick and dirty solution but I believe it is a bit dangerous in that it may fill up the event queue if there are a large number of callbacks competing for the same lock. I don't actually know how bad this is but I imagine it could be a problem if someone were to intentionally fill up the event queue with tons of txs which are from the same sender.
Solutions?
I don't know the best way to approach this I just intuit there's a solution I haven't yet explored. Maybe to do with queues and promises, but my thoughts in this area are not developed enough to describe at the moment.
I'd love feedback!
Some resources I've taken a look at...
The text was updated successfully, but these errors were encountered: