Implement BlockManager in Rust#1637
Conversation
| } | ||
|
|
||
| struct GetBlockIterator<'a> { | ||
| blockstore: &'a InMemoryBlockStore, |
There was a problem hiding this comment.
This probably shouldn't be tied to InMemoryBlockStore, or should be called InMemoryBlockStoreIterator
There was a problem hiding this comment.
I can probably do &'a BlockStore, because all we care about is that it is a BlockStore.
| } | ||
|
|
||
| struct Anchor { | ||
| pub blockstore: String, |
There was a problem hiding this comment.
Probably would name this blockstore_name
| left.block_num as i64 - right.block_num as i64 | ||
| } | ||
|
|
||
| fn walk_back_1_from_block(&self, block_id: String) -> Option<&'a Block> { |
There was a problem hiding this comment.
get_previous_block seems like a better name
There was a problem hiding this comment.
Thanks, Good suggestion.
| .unwrap_or(None) | ||
| } | ||
|
|
||
| fn walk_back_n_from_block(&self, block_id: String, n: u64) -> Option<&'a Block> { |
There was a problem hiding this comment.
Likewise, get_nth_previous_block
There was a problem hiding this comment.
Thanks, good suggestion.
| } | ||
|
|
||
| fn iter(&self) -> Box<Iterator<Item = Block>> { | ||
| unimplemented!() |
There was a problem hiding this comment.
Doesn't look like you're using the Iterator below.
There was a problem hiding this comment.
The iterator below is used in get.
There was a problem hiding this comment.
Is this going to be implemented, or is it intentionally unimplemented?
There was a problem hiding this comment.
I didn't need it for any of the work I had done so far, so I didn't implement it. Can you talk about iterating through a blockstore that wouldn't be like branch or branch_diff where you are starting from some known tip?
| blockstore_name: blockstore_name.to_string(), | ||
| external_ref_count, | ||
| internal_ref_count, | ||
| block_id: block_id.to_string(), |
There was a problem hiding this comment.
Per previous style discussions, prefer into() for string conversions like this.
| } | ||
|
|
||
| #[derive(Default)] | ||
| struct Anchors { |
There was a problem hiding this comment.
Please add a docstring explaining why this struct exists.
| let predecessor_of_head_in_any_blockstore = self.blockstore_by_name.values().any( | ||
| |blockstore| blockstore.get(vec![head.previous_block_id.clone()]).count() > 0, | ||
| ); | ||
| let head_is_genesis = head.previous_block_id == NULL_BLOCK_IDENTIFIER; |
There was a problem hiding this comment.
I wouldn't use blockchain terminology here, just think of it as a tree, so this would be the root. Also, I think it should be possible to avoid doing root-node-specific checks by making the root nodes anchors.
There was a problem hiding this comment.
I'm not sure it is possible to do without the NULL_BLOCK_IDENTIFIER check. If the first put is C 3, D 4 and with C pointing to an unknown block B is it ok or is it an error?
There was a problem hiding this comment.
I am thinking that when you create the block manager, rather than having anchors be empty, you could initialize it with an anchor to a block with id = NULL_BLOCK_IDENTIFIER. But there might be some other reason not to do this.
|
|
||
| if !(predecessor_of_head_in_memory || predecessor_of_head_in_any_blockstore | ||
| || head_is_genesis) | ||
| { |
There was a problem hiding this comment.
This check should probably be refactored behind a contains function.
| #[derive(Debug, PartialEq)] | ||
| pub enum BlockManagerError { | ||
| MissingPredecessor(String), | ||
| MissingPredecessorInBranch, |
There was a problem hiding this comment.
This should probably take the predecessor that was missing.
|
|
||
| #[derive(Debug, PartialEq)] | ||
| pub enum BlockManagerError { | ||
| MissingPredecessor(String), |
There was a problem hiding this comment.
Similar to below, this should add a clarifying phrase about where the predecessor was expected.
| } | ||
|
|
||
| impl<'a> BranchIterator<'a> { | ||
| pub fn new(block_manager: &'a BlockManager, first_block_id: String) -> Self { |
There was a problem hiding this comment.
I think that when you create this, it should ref the first_block_id and then when it is dropped, it should unref the first_block_id. That way you can guarantee the iterator is always valid.
There was a problem hiding this comment.
Ah, that makes sense, so that blocks aren't being dropped while the Iterator is iterating.
There was a problem hiding this comment.
@aludvik That will change the get method from &self to &mut self. does that seem ok?
|
|
||
| #[derive(Default)] | ||
| pub struct BlockManager { | ||
| block_by_block_id: HashMap<String, (Block, i64, i64)>, |
There was a problem hiding this comment.
I would use a struct here instead of a tuple so it is clearer which i64 is for internal/external.
There was a problem hiding this comment.
Can we use a u64 here with checked subtraction to catch when we try to unref below 0? https://doc.rust-lang.org/std/primitive.u64.html#method.checked_sub
|
|
||
| let (external_ref_count, internal_ref_count, block_id) = ref_count_block_id_result?; | ||
|
|
||
| if external_ref_count <= 0 && internal_ref_count <= 0 { |
There was a problem hiding this comment.
I think this should be split up into checking whether the count is less than 0, which should be a panic, and checking if the count is 0, which should cause a purge.
| } | ||
|
|
||
| fn iter(&self) -> Box<Iterator<Item = Block>> { | ||
| unimplemented!() |
There was a problem hiding this comment.
Is this going to be implemented, or is it intentionally unimplemented?
| "During Put, missing predecessor of block {}: {}", | ||
| head.header_signature, head.previous_block_id | ||
| ))); | ||
| } else { |
There was a problem hiding this comment.
This doesn't need to be else, since you are returning above.
| ), | ||
| )); | ||
| } | ||
| previous = Some(block.header_signature.clone()); |
| } else { | ||
| let mut previous = None; | ||
|
|
||
| for block in tail { |
There was a problem hiding this comment.
This internal branch integrity check feels like a good candidate for refactoring into a separate private helper function with a descriptive name so it is easier to tell what is going on.
| !self.block_by_block_id.contains_key(&block.header_signature) | ||
| && !self.blockstore_by_name.values().any(|blockstore| { | ||
| blockstore.get(vec![block.header_signature.clone()]).count() > 0 | ||
| }) |
There was a problem hiding this comment.
Can you just use self.contains() here?
| blockstore.get(vec![block.header_signature.clone()]).count() > 0 | ||
| }) | ||
| }) | ||
| .collect(); |
There was a problem hiding this comment.
Can you avoid the extra vector allocation here by chaining a for_each() here instead? The you can just do if let Some(last_block) = branch.last() after?
There was a problem hiding this comment.
I wouldn't do branch.last() because branch has been consumed and it is logically a different thing from blocks_not_added_yet.
| if let Some((last_block, blocks)) = blocks_not_added_yet.split_last() { | ||
| self.block_by_block_id.insert( | ||
| last_block.header_signature.clone(), | ||
| (last_block.clone(), 0, 0), |
There was a problem hiding this comment.
I think this needs an external reference count of 1 to start.
There was a problem hiding this comment.
Just so I understand, the blocks that go in with an internal reference count of 0 as well as ones that have an internal reference count of 1 would all have an external reference count of 1 to start.
|
|
||
| let mut optional_new_tip = None; | ||
|
|
||
| let (external_ref_count, internal_ref_count, block_id) = ref_count_block_id_result?; |
There was a problem hiding this comment.
Is there a reason to separate these lines instead of just doing: let (external_ref_count, internal_ref_count, block_id) = self.lower_tip_blocks_refcount(tip)?;?
The BlockStore trait is only added so that the full BlockManager interface can be shown. A blockstore implementation will be delayed until there is work on persist and add_store for the BlockManager. Signed-off-by: Boyd Johnson <bjohnson@bitwise.io>
Signed-off-by: Boyd Johnson <bjohnson@bitwise.io>
The `get` method returns an iterator over the blocks that are being asked for. Signed-off-by: Boyd Johnson <bjohnson@bitwise.io>
The `ref_block` puts an external hold on the particular block that the BlockManager already has. Signed-off-by: Boyd Johnson <bjohnson@bitwise.io>
This method considers blocks in order from some branch tip and removes all blocks up to any block that has an external hold or a ref-count of 2+. Signed-off-by: Boyd Johnson <bjohnson@bitwise.io>
Signed-off-by: Boyd Johnson <bjohnson@bitwise.io>
The test for this method only tests blocks that are not in a blockstore. More tests will be added when persist and add_store are added. Signed-off-by: Boyd Johnson <bjohnson@bitwise.io>
Signed-off-by: Boyd Johnson <bjohnson@bitwise.io>
The branch_diff gives a difference between two chains. C D block_manager.branch_diff tip<- C, exclude <- D \/ will give the single block in the branch C. B | A Signed-off-by: Boyd Johnson <bjohnson@bitwise.io>
The `persist` method inserts blocks into a named blockstore and removes the other fork from that blockstore. Anchors for blocks with a non-zero reference count are created. Signed-off-by: Boyd Johnson <bjohnson@bitwise.io>
Signed-off-by: Boyd Johnson <bjohnson@bitwise.io>
This work is based off of the ideas in this RFC pull request: hyperledger-archives/sawtooth-rfcs#5.
The persist method is not yet implemented, but I wanted to get eyes on the work so far.