-
Notifications
You must be signed in to change notification settings - Fork 158
Wait for tx pool re-org when emitting pending transaction events #1323
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
da88c79
to
7bfe897
Compare
plugin/evm/vm.go
Outdated
case <-vm.shutdownChan: | ||
return | ||
case event := <-events: | ||
if event.Head == nil { |
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 event.Head
be nil or event.Head.Number
be nil?
|
||
if b.ethBlock != nil { | ||
vm.setPendingBlock(b.ethBlock.NumberU64()) | ||
} else { |
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 this happen?
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.
no, we do check this in verification
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.
Few comments, I think we should move most of the logic to builder.
plugin/evm/vm.go
Outdated
blockNumLock sync.RWMutex | ||
pendingBlockNum uint64 | ||
latestBlockNum uint64 |
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.
should we just push these to builder?
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.
Moved it
plugin/evm/vm.go
Outdated
events := make(chan core.NewTxPoolReorgEvent) | ||
sub := vm.txPool.SubscribeNewReorgEvent(events) | ||
vm.shutdownWg.Add(1) | ||
go func() { | ||
vm.subscribeToTxPoolEvents(events) | ||
sub.Unsubscribe() | ||
vm.shutdownWg.Done() | ||
}() |
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 we use builder to subscribe this? I think generally block builder should be a separate entity from vm.
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.
Moved it
plugin/evm/vm.go
Outdated
vm.blockNumLock.Lock() | ||
defer vm.blockNumLock.Unlock() | ||
vm.pendingBlockNum = blockNum | ||
} |
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.
again can be part of block builder
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.
Moved it
err = vm.blockChain.SetPreference(block.GetEthBlock()) | ||
if err == nil { | ||
vm.setPendingBlock(block.GetEthBlock().NumberU64()) | ||
} | ||
return err | ||
} |
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.
err = vm.blockChain.SetPreference(block.GetEthBlock()) | |
if err == nil { | |
vm.setPendingBlock(block.GetEthBlock().NumberU64()) | |
} | |
return err | |
} | |
err = vm.blockChain.SetPreference(block.GetEthBlock()) | |
if err != nil { | |
return err | |
} | |
vm.setPendingBlock(block.GetEthBlock().NumberU64()) | |
return err | |
} |
plugin/evm/vm.go
Outdated
func (vm *VM) subscribeToTxPoolEvents(events <-chan core.NewTxPoolReorgEvent) { | ||
for { | ||
select { | ||
case <-vm.shutdownChan: |
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 we use the ctx in onNormalOperationsStarted
?
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 we can
"height", b.Height(), | ||
) | ||
|
||
if b.ethBlock != nil { |
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.
why do we set this here? I think setting this in SetPreference
should be enough?
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.
Because we later call vm.blockchain.Accept() and then setPreference.
plugin/evm/vm.go
Outdated
vm.builder = vm.NewBlockBuilder(vm.extensionConfig.ExtraMempool, func() bool { | ||
vm.blockNumLock.RLock() | ||
defer vm.blockNumLock.RUnlock() | ||
return vm.pendingBlockNum != vm.latestBlockNum |
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'm confused with this equation doesn't this mean
b.pendingPoolUpdate
will return true if they're not equal and it will keep waiting?
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.
Exactly, if they are not equal - it means there is a pending mempool re-org, so we need to keep waiting.
If they are equal, then there is no pending mempool re-org and it is safe to return the pending event.
plugin/evm/vm.go
Outdated
vm.blockNumLock.Lock() | ||
vm.latestBlockNum = event.Head.Number.Uint64() | ||
vm.blockNumLock.Unlock() | ||
vm.builder.signalCanBuild() |
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.
isn't it enough to check event.Head
against vm.pendingBlockNumber
and then signal/not signal the builder? why do we need vm.latestBlockNum
?
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.
We wait on the condition variable:
for !b.needToBuild() || b.pendingPoolUpdate() {
if err := b.pendingSignal.Wait(ctx); err != nil {
return time.Time{}, common.Hash{}, err
}
}
In case we either don't need to build a new block, or there is a pending pool update.
If we need to build a new block but there is a pending pool update, we need to wait on the condition variable.
If we don't check the pending pool update in case we need to build a block, then we won't wait on the condition variable and will immediately try to build a block and may suffer the false positive.
The signal is done to wake up and check again if the mempool re-org has finished, because without the signal we may wait on the condition variable and not wake up once the re-org has finished.
Thanks for the quick review, addressed your comments! |
Currently whenever we ask the VM to wait for an event, it checks if the mempool has transactions in it, and if so, it proceeds to return a "pending transactions" event. The mempool has or has not transactions in it according to the block that the VM is configured to build on top on. When the VM is being told to change the preferred block to build the next block on, the mempool is asynchronously re-organized in the background according to the block preference change. Since the mempool re-organization happens asynchronously and in the background, when a VM changes its preference and is immediately asked to build a new block, the mempool may still contain transactions in it, because the mempool re-organization is still being performed asynchronously in the background. This leads to a false positive, as the mempool may not have transactions in it after its re-organization, but the WaitForEvent API call may return that the mempool has transactions in it and would result in building a block that shouldn't have been built. This PR fixes this false positive, as explained below: When we set the preference in the VM or accept a block, we mark the block height to be pending. In parallel, we subscribe to updates from the mempool which are sent only once the re-org corresponding to a block has finished. Upon such an update, we mark the block as the latest block. When the VM checks whether to build a block, additionally to checking whether there are transactions in the mempool, we now also check whether the mempool is pending a re-org. A mempool is pending a re-org if the block number that is set when the block is accepted or the preference changes (the pending number) is different than the block number that is received in the latest subscription update from the mempool (the latest number).
Signed-off-by: Yacov Manevich <[email protected]>
Signed-off-by: Yacov Manevich <[email protected]>
Why this should be merged
Currently whenever we ask the VM to wait for an event, it checks if the mempool has transactions in it, and if so, it proceeds to return a "pending transactions" event.
The mempool has or has not transactions in it according to the block that the VM is configured to build on top on.
When the VM is being told to change the preferred block to build the next block on, the mempool is asynchronously re-organized in the background according to the block preference change.
Since the mempool re-organization happens asynchronously and in the background, when a VM changes its preference and is immediately asked to build a new block, the mempool may still contain transactions in it, because the mempool re-organization is still being performed asynchronously in the background. This leads to a false positive, as the mempool may not have transactions in it after its re-organization, but the
WaitForEvent
API call may return that the mempool has transactions in it and would result in building a block that shouldn't have been built.This PR fixes this false positive, as explained below.
How this works
When we set the preference in the VM or accept a block, we mark the block height to be
pending
.In parallel, we subscribe to updates from the mempool which are sent only once the re-org corresponding to a block has finished. Upon such an update, we mark the block as the
latest
block.When the VM checks whether to build a block, additionally to checking whether there are transactions in the mempool, we now also check whether the mempool is pending a re-org. A mempool is pending a re-org if the block number that is set when the block is accepted or the preference changes (the
pending
number) is different than the block number that is received in the latest subscription update from the mempool (thelatest
number).How this was tested
Rename
TestWaitForEvent
from the PR totestWaitForEvent
and createTestWaitForEvent
as follows:The test passes but miserably fails in master.
Need to be documented?
No
Need to update RELEASES.md?
No