-
Notifications
You must be signed in to change notification settings - Fork 764
Implement BlockManager in Rust #1637
Implement BlockManager in Rust #1637
Conversation
validator/src/journal/block_store.rs
Outdated
} | ||
|
||
struct GetBlockIterator<'a> { | ||
blockstore: &'a InMemoryBlockStore, |
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.
This probably shouldn't be tied to InMemoryBlockStore
, or should be called InMemoryBlockStoreIterator
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 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably would name this blockstore_name
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.
Cool, Thanks!
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
get_previous_block
seems like a better name
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Likewise, get_nth_previous_block
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.
Thanks, good suggestion.
validator/src/journal/block_store.rs
Outdated
} | ||
|
||
fn iter(&self) -> Box<Iterator<Item = Block>> { | ||
unimplemented!() |
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.
Doesn't look like you're using the Iterator below.
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.
The iterator below is used in get
.
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.
Is this going to be implemented, or is it intentionally unimplemented?
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 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Per previous style discussions, prefer into()
for string conversions like this.
} | ||
|
||
#[derive(Default)] | ||
struct Anchors { |
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should probably take the predecessor that was missing.
|
||
#[derive(Debug, PartialEq)] | ||
pub enum BlockManagerError { | ||
MissingPredecessor(String), |
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, that makes sense, so that blocks aren't being dropped while the Iterator is iterating.
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.
@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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
validator/src/journal/block_store.rs
Outdated
} | ||
|
||
fn iter(&self) -> Box<Iterator<Item = Block>> { | ||
unimplemented!() |
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't need to be else, since you are returning above.
), | ||
)); | ||
} | ||
previous = Some(block.header_signature.clone()); |
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.
This clone seems avoidable
} else { | ||
let mut previous = None; | ||
|
||
for block in tail { |
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you just use self.contains()
here?
blockstore.get(vec![block.header_signature.clone()]).count() > 0 | ||
}) | ||
}) | ||
.collect(); |
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 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this needs an external reference count of 1 to start.
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 <[email protected]>
Signed-off-by: Boyd Johnson <[email protected]>
The `get` method returns an iterator over the blocks that are being asked for. Signed-off-by: Boyd Johnson <[email protected]>
The `ref_block` puts an external hold on the particular block that the BlockManager already has. Signed-off-by: Boyd Johnson <[email protected]>
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 <[email protected]>
Signed-off-by: Boyd Johnson <[email protected]>
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 <[email protected]>
Signed-off-by: Boyd Johnson <[email protected]>
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 <[email protected]>
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 <[email protected]>
Signed-off-by: Boyd Johnson <[email protected]>
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.