Seed the canonical chain with the first block in the monitored head set#193
Conversation
Signed-off-by: Peter Broadhurst <peter.broadhurst@kaleido.io>
| if seedBi != nil { | ||
| notifyPos = bl.reconcileCanonicalChain(seedBi) | ||
| seedBi = nil | ||
| } else { | ||
| rpcErr := bl.backend.CallRPC(bl.ctx, &blockHashes, "eth_getFilterChanges", filter) | ||
| if rpcErr != nil { | ||
| if etherrors.MapError(etherrors.FilterRPCMethods, rpcErr.Error()) == ffcapi.ErrorReasonNotFound { | ||
| log.L(bl.ctx).Warnf("Block filter '%v' no longer valid. Recreating filter: %s", filter, rpcErr.Message) | ||
| filter = "" | ||
| gapPotential = true | ||
| } | ||
| log.L(bl.ctx).Errorf("Failed to query block filter changes: %s", rpcErr.Message) | ||
| failCount++ | ||
| continue | ||
| } | ||
| log.L(bl.ctx).Errorf("Failed to query block filter changes: %s", rpcErr.Message) | ||
| failCount++ | ||
| continue | ||
| log.L(bl.ctx).Debugf("Block filter received new block hashes: %+v", blockHashes) |
There was a problem hiding this comment.
This highlights the trade-off difference between the current design and the proposed changes.
Current design builds the in-memory chain from the current head:
- The in-memory chain always starts from the head block
- No catch-up work is carried out for in-memory chain building. Therefore, minimal delay in indexing new blocks
The proposed design:
- The in-memory chain sometimes starts from the head block (current head is less than the expected length / fail to get the seed block information), and sometimes starts from a historical block.
- Catch-up is required for the in-memory chain. Depending on how many historical blocks need to be fetched and how quickly they can be fetched, there could be a visible delay in fetching any new blocks from the chain
The current design serves a primary purpose of tracking the new blocks in a timely manner and optionally provides an in-memory chain for efficient caching.
The proposed design compromises the primary purpose.
@peterbroadhurst I wonder whether the bug you are trying to fix is in the logic that uses the in-memory chain? If it sees that the head of the in-memory chain is already sufficient to confirm an item, it should then fetch the blocks that are not in the in-memory chain and complete the confirmation itself, rather than wait for the in-memory chain. <--- This should already be the behaviour of ReconcileConfirmationsForTransaction function that the connector provides.
There was a problem hiding this comment.
The in-memory chain always starts from the head block
No. There is no change here.
The current design is the in-memory canonical chain starts from the monitored head length back from the head of the chain. This just changes how it's constructed.
There was a problem hiding this comment.
This should already be the behaviour of ReconcileConfirmationsForTransaction function that the connector provides.
This sounds like the misunderstanding @Chengxuan .
The code never goes back and removes the first block in the in-memory list - unless it's no longer on the blockchain (or wraps the monitored head length obviously).
So this new code is just to ensure this block is established on startup at the beginning of the monitored head length, rather than the end of it.
Signed-off-by: Peter Broadhurst <peter.broadhurst@kaleido.io>
|
Note I'm seeing intermittent errors on builds relating to timing, which has been a historical problem in this package. |
Signed-off-by: Peter Broadhurst <peter.broadhurst@kaleido.io>
Simplify the blocklistener_test.go with wrappers on mocks
Signed-off-by: Peter Broadhurst <peter.broadhurst@kaleido.io>
|
@peterbroadhurst I feel I'm missing a trivial point here. Let me use an example to illustrate my understanding. ExampleAt the connector start-up time, the latest block from the chain is 1000. I have a transaction that's mined in block 1, requires 5 confirmations. Current behaviour:
^^ even with no new blocks indexed, confirmation should still happen through catchup Proposed behaviour:
The difference to me is:
|
|
Thanks for discussing this with me @Chengxuan - and I hope we've agreed that your analysis of one potential use of this code fulfilling it's expected behavior, is accurate, but also not a reason to continue with the code not doing the job it promises to do. Function of code:
Bug fixed by this PR:
Indirect implication of bug (which we over indexed on, sorry):
The area we got stuck in the conversation, and thanks for working through:
|
Chengxuan
left a comment
There was a problem hiding this comment.
@peterbroadhurst, thanks for the explanation on the behaviour that's being fixed.
Changes look good to me. One minor comment about the current logic is that it still relies on at least 1 new block to trigger the rebuild of the floating window.
Leaving it for you to consider the severity of that gap
| gapPotential = true | ||
| var notifyPos *list.Element | ||
| if seedBi != nil { | ||
| notifyPos = bl.reconcileCanonicalChain(seedBi) |
There was a problem hiding this comment.
note: Given the rebuild/catch-up of the in-memory chain relies on the new block not matching the existing tail block in the in-memory chain. So this first iteration here will only initiate the in-memory chain. The actual rebuild happens in the next iteration if at least 1 block hash has been discovered.
When we restart EVMConnect with a significant number of confirmations, it takes a long time for the block listener to confirm any new blocks, or to allow listeners to restart catchup.
This is because we are beginning creating the canonical in-memory chain from the head, and that means the number of confirmations worth of blocks (say 20 for example) need to arrive before we have enough blocks in-memory to confirm any block.
Whereas in reality, there might be a number of blocks mined during the since we stopped (maybe 100s or 1000s) that are fully confirmed now.
This PR proposes that we:
reconcileCanonicalChainon the first loop iteration