Skip to content
This repository has been archived by the owner on Feb 1, 2024. It is now read-only.

Implement BlockManager in Rust #1637

Merged
merged 11 commits into from
May 16, 2018
Merged

Implement BlockManager in Rust #1637

merged 11 commits into from
May 16, 2018

Conversation

boydjohnson
Copy link
Contributor

@boydjohnson boydjohnson commented May 11, 2018

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.

@boydjohnson boydjohnson self-assigned this May 11, 2018
@boydjohnson boydjohnson changed the title Implementation of BlockManager in Rust Implement BlockManager in Rust May 11, 2018
}

struct GetBlockIterator<'a> {
blockstore: &'a InMemoryBlockStore,
Copy link
Contributor

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

Copy link
Contributor Author

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,
Copy link
Contributor

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

Copy link
Contributor Author

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> {
Copy link
Contributor

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

Copy link
Contributor Author

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> {
Copy link
Contributor

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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, good suggestion.

}

fn iter(&self) -> Box<Iterator<Item = Block>> {
unimplemented!()
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link

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?

Copy link
Contributor Author

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(),
Copy link

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 {
Copy link

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;
Copy link

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.

Copy link
Contributor Author

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?

Copy link

@aludvik aludvik May 14, 2018

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)
{
Copy link

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,
Copy link

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),
Copy link

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 {
Copy link

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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)>,
Copy link

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.

Copy link

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 {
Copy link

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.

}

fn iter(&self) -> Box<Iterator<Item = Block>> {
unimplemented!()
Copy link

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 {
Copy link

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());
Copy link

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 {
Copy link

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
})
Copy link

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();
Copy link

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?

Copy link
Contributor Author

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),
Copy link

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.

Copy link
Contributor Author

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?;
Copy link

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)?;?

Boyd Johnson added 11 commits May 15, 2018 15:19
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]>
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]>
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]>
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]>
@boydjohnson boydjohnson merged commit 715b72e into hyperledger-archives:master May 16, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Development

Successfully merging this pull request may close these issues.

3 participants