-
Notifications
You must be signed in to change notification settings - Fork 118
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(state): Implements reconsider_block method #9260
base: main
Are you sure you want to change the base?
feat(state): Implements reconsider_block method #9260
Conversation
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.
Thank you! This looks good, I left a suggestion for some cleanup, but there are no blockers to merging this as-is if preferred.
…m validate_and_commit if a candidate block's hash is in the map of invalidated blocks. Stores invalidated_blocks by height and clears when finalizing. Checks against non finalized tip hash to create a new chain if parnt_chain doesn't exist. Renames ReconsiderError variant NonPreviouslyInvalidatedBlock to MissingInvalidatedBlock.
This pull request has been removed from the queue for the following reason: Pull request #9260 has been dequeued, merge conditions unmatch:
You should look at the reason for the failure and decide if the pull request needs to be fixed or if you want to requeue it. If you want to requeue this pull request, you need to post a comment with the text: |
/// | ||
/// This limits the memory for each entry to around: | ||
/// `256 blocks * 2 MB per block = 0.5 GB` | ||
pub const MAX_INVALIDATED_BLOCKS: usize = 256; |
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.
@arya2 Does this make sense to cap it at 256?
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 is looking great, thank you for adding the IndexMap
.
There's one small issue around adding chains to the non-finalized state with roots below the finalized tip, but otherwise this PR looks ready to merge.
@@ -235,6 +237,10 @@ impl NonFinalizedState { | |||
self.insert(side_chain); | |||
} | |||
|
|||
// Remove all invalidated_blocks at or below the finalized height | |||
self.invalidated_blocks | |||
.retain(|height, _blocks| *height > best_chain_root.height); |
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.
If you add a handle to the db as a parameter on reconsider_block()
, this should leave invalidated blocks until they are below the best chain root height.
.retain(|height, _blocks| *height > best_chain_root.height); | |
.retain(|height, _blocks| *height >= best_chain_root.height); |
/// The maximum number of invalidated blocks per entry. | ||
/// | ||
/// This limits the memory for each entry to around: | ||
/// `256 blocks * 2 MB per block = 0.5 GB` | ||
pub const MAX_INVALIDATED_BLOCKS: usize = 256; |
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.
If the collection could be mutated by peers, it would be better to track the size of the blocks and start dropping the first ones that were inserted until it's below a size limit, but limiting the number of entries is good enough here as invalidated blocks should only be added via a private RPC interface.
/// The maximum number of invalidated blocks per entry. | |
/// | |
/// This limits the memory for each entry to around: | |
/// `256 blocks * 2 MB per block = 0.5 GB` | |
pub const MAX_INVALIDATED_BLOCKS: usize = 256; | |
/// The maximum number of invalidated block records. | |
/// | |
/// This limits the memory use to around: | |
/// `100 entries * up to 99 blocks * 2 MB per block = 20 GB` | |
pub const MAX_INVALIDATED_BLOCKS: usize = 100; |
let parent_chain = self | ||
.parent_chain(root_parent_hash) | ||
.map_err(|_| ReconsiderError::ParentChainNotFound(block_hash)); | ||
|
||
let modified_chain = match parent_chain { | ||
// 1. If a parent chain exist use the parent chain | ||
Ok(parent_chain) => { | ||
let mut chain = Arc::unwrap_or_clone(parent_chain); | ||
|
||
for block in Arc::unwrap_or_clone(invalidated_blocks) { | ||
chain = chain.push(block)?; | ||
} | ||
|
||
Arc::new(chain) | ||
} | ||
// 2. If a parent chain does not exist create a new chain from the non finalized state tip | ||
Err(_) => { | ||
let mut chain = Chain::new( | ||
&self.network, | ||
invalidated_root.height.previous().map_err(|_| { | ||
ReconsiderError::InvalidHeight(Height(invalidated_root.height.0 - 1)) | ||
})?, | ||
Default::default(), | ||
Default::default(), | ||
Default::default(), | ||
Default::default(), | ||
ValueBalance::zero(), | ||
); | ||
|
||
for block in Arc::unwrap_or_clone(invalidated_blocks) { | ||
chain = chain.push(block)?; | ||
} | ||
|
||
Arc::new(chain) | ||
} | ||
}; |
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.
parent_chain()
will already return a new fork if the invalidated block's parent is in the non-finalized state. This method shouldn't add a new chain to the non-finalized state if it's root is below the finalized tip.
This method could accept a handle to the db as a parameter and create a new chain if the invalidated block's parent is the finalized tip block, or it could refuse to reconsider blocks unless their parent is in the non-finalized state.
The suggestion below does the latter:
let parent_chain = self | |
.parent_chain(root_parent_hash) | |
.map_err(|_| ReconsiderError::ParentChainNotFound(block_hash)); | |
let modified_chain = match parent_chain { | |
// 1. If a parent chain exist use the parent chain | |
Ok(parent_chain) => { | |
let mut chain = Arc::unwrap_or_clone(parent_chain); | |
for block in Arc::unwrap_or_clone(invalidated_blocks) { | |
chain = chain.push(block)?; | |
} | |
Arc::new(chain) | |
} | |
// 2. If a parent chain does not exist create a new chain from the non finalized state tip | |
Err(_) => { | |
let mut chain = Chain::new( | |
&self.network, | |
invalidated_root.height.previous().map_err(|_| { | |
ReconsiderError::InvalidHeight(Height(invalidated_root.height.0 - 1)) | |
})?, | |
Default::default(), | |
Default::default(), | |
Default::default(), | |
Default::default(), | |
ValueBalance::zero(), | |
); | |
for block in Arc::unwrap_or_clone(invalidated_blocks) { | |
chain = chain.push(block)?; | |
} | |
Arc::new(chain) | |
} | |
}; | |
let parent_chain = self | |
.parent_chain(root_parent_hash) | |
.map_err(|_| ReconsiderError::ParentChainNotFound(block_hash))?; | |
let mut modified_chain = Arc::unwrap_or_clone(parent_chain); | |
for block in Arc::unwrap_or_clone(invalidated_blocks) { | |
modified_chain = modified_chain.push(block)?; | |
} |
#[error("Invalid height {0:?}: parent height would be negative")] | ||
InvalidHeight(block::Height), | ||
|
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 is no longer needed if the previous suggestion is applied.
#[error("Invalid height {0:?}: parent height would be negative")] | |
InvalidHeight(block::Height), |
Motivation
The changes create and test a reconsider_block method to later be integrated into the zebra RPC. This method reconsiders previously invalidated_blocks into a chain that exist in the chain_set. Closes #8842.
Specifications & References
NonFinalizedState
via RPC methods #8634reconsider_block()
method to theNonFinalizedState
for validating/committing a previously invalidated block and its invalidated descendents #8842Solution
reconsider_block
method toNonFinalizedState
that allows previously invalidated blocks to be reconsidered and re-inserted into the chain.Tests
Follow-up Work
PR Author's Checklist
PR Reviewer's Checklist