Open brenzi opened 5 months ago
Alright, so far I agree with the status quo, and the general implementation, I have some low-level questions:
execute_trusted_calls
part? I think it is unrelated, and everything could be done when we realize that there is a new finalized sidechain block. However, it could be that your proposal is the lowest hanging fruit here. I need some more time to evaluate that, in the long run though, I definitely argue that execute trusted calls, just needs to know the best state, and should not care about forks.
- I don't really understand why prune is necessary after we finalize nodes. Shouldn't the re-rooted tree be pruned already?
if it's like that, ok. Looking at the pub fn
of ForkTree I expected this to be explicit. And I think explicit may be better because we may want to keep the blocks around a little longer for the benefit of peers syncing up
- I think we need to add that we must re-build our state (re-apply sidechain blocks), if the child of the longest chain changes after rebalancing. (However, the fork-tree is rebalanced automatically upon block import, so I am not sure if rebalancing has to be done at this step, but rebuilding the 'best' state is still needed IMO).
we can't roll back. If rebalancing changes what we consider to be the longest chain, we need to reset on a snapshot and reapply blocks
- We should also mark in our db, what state is finalized, or we should only persist finalized state anyhow, probably.
We should only snapshot finalized state
Persistance I'm not yet sure about. For simplicity, I suggest to snapshot every directly finalized block. Everything else (ForkTree and state after non-finalized blocks) might be kept in-mem
- under what circumstances can there be no valid child? When a node has been finalized that is not in our fork tree?
that we may be able to sync from a peer. no valid child is the situation when - even after sync from peers - we have no finalized block or lineal descendant of such
- Why does finalizing nodes in the fork tree have to be in the
execute_trusted_calls
part? I think it is unrelated, and everything could be done when we realize that there is a new finalized sidechain block. However, it could be that your proposal is the lowest hanging fruit here. I need some more time to evaluate that, in the long run though, I definitely argue that execute trusted calls, just needs to know the best state, and should not care about forks.
My design choice is pragmatism. I saw how it can fit there and I see no reason why it wouldn't be a good idea. It's even fit for the (potential) future case of more than one shard per validateer service
So the biggest chunks in terms of implementation efforts that can be estimated should be:
ImportQueue
with the ForkTree
structFinalize(latest_sidechain_block_confirmation)
:
Goal:
simplifying assumptions:
requires
status quo
EnclaveSidechainBlockImportQueue = ImportQueue<SignedSidechainBlock>
FCFSyield_next_slot
-> propose a new blockRPC
sidechain_fetchBlocksFromPeer
(cannot request blocks on branches we don't know they exist)Proposed Implementation
ImportQueue<SignedSidechainBlock>
withForkTree<Hash, SidechainBlockNumber, SignedSidechainBlock>
ForkTree::import()
(async)enclave_runtime::execute_trusted_calls_internal()
:ForkTree::finalize_with_ancestors(latest_sidechain_block_confirmation)
ForkTree::prune()
(possibly pruning with delay of a few block finalizations to service peers if needed)ForkTree::rebalance()
Initial behavior
RPC changes
sidechain_fetchAvailableBlockHashes
: returns all block hashes in the fork tree (which is regularly pruned upon finalization)sidechain_fetchBlocksFromPeer
: request set of blocks from peer specified by hash without assuming canonical rangeArgumentation
ForkTree
happens async and doesn't cost block production time (in contrast to building theForkTree
fromImportQueue
synchronously uponexecute_trusted_calls()
)