-
Notifications
You must be signed in to change notification settings - Fork 419
feat(chain)!: Add prev_blockhash to ToBlockHash trait
#2091
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
base: master
Are you sure you want to change the base?
feat(chain)!: Add prev_blockhash to ToBlockHash trait
#2091
Conversation
|
How does this relate to PR #2024? That PR addresses the same issue and handles validation in I'm also wondering if this is meant to show that we can achieve simpler validation by not introducing optional data fields. However, there is a gap: without tracking "ghost checkpoints" (positions inferred from For example, if I insert a checkpoint at height 100 with
The current PR doesn't address either path, so I don't think it fully resolves #2021. Is there a different approach you have in mind for handling these cases? |
|
Here we don't intend to make the It's missing coverage for when The previous /// Get the previous [`BlockId`] of this checkpoint.
///
/// I.e. the height and hash of the block immediately preceding this one by consensus,
/// if it can be inferred from the `prev_blockhash` of the inner block data.
pub fn prev_block_id(&self) -> Option<BlockId> {
let prev_height = self.height().checked_sub(1)?;
let prev_hash = self.0.data.prev_blockhash()?;
Some(BlockId {
height: prev_height,
hash: prev_hash,
})
} |
a2ea12f to
d5b2513
Compare
prev_blockhash to trait ToBlockHashprev_blockhash to trait ToBlockHash
evanlinjin
left a comment
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.
Since you're taking over ticket #2021, feel free to checkout my unfinished work for inspiration: https://github.com/bitcoindevkit/bdk/compare/cp_entry.
crates/core/src/checkpoint.rs
Outdated
| // implication invalid. | ||
| tail = vec![]; | ||
| break cp.prev().expect("can't be called on genesis block"); | ||
| // We're replacing a block, so the tail is no longer 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.
We should also displace the tail if:
data'sprev_blockhashconflicts with the block directly below. The block directly below should also be displaced.data'sblockhashconflicts with the theprev_blockhashof the block directly above.
To achieve this, I recommend using CheckPointEntry instead of CheckPoint and add a method such as CheckPointEntry::prev() - this requires the CheckPointEntry::BlockId variant to have a CheckPoint field.
Naming suggestion
/// An entry yielded by [`CheckPointEntryIter`].
#[derive(Debug, Clone)]
pub enum CheckPointEntry<D> {
/// A placeholder entry: there is no checkpoint stored st this height,
/// but the checkpoint one height above links back here via it's `prev_blockhash`.
Placeholder {
/// The block ID at *this* height.
block_id: BlockId,
/// The checkpoint one height *above* that links back to `block_id`.
checkpoint_above: CheckPoint<D>,
},
/// A real checkpoint recorded at this height.
Occupied(CheckPoint<D>),
}| /// (a.k.a. the update and original chain both have a block above the point of agreement, but | ||
| /// their heights do not overlap). | ||
| /// - The update attempts to replace the genesis block of the original chain. | ||
| fn merge_chains( |
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 refactored merge_chains does not do anything meaningfully different. It also does not make it easier to read.
The important change to make is to make sure merge_chains takes prev_blockhashes into consideration. The refactoring does not do that.
Suggested changes
- Iterate over
CheckPointEntryinstead ofCheckPoint. Rationale:prev_blockhashes are also block hashes so they can be points of agreements/invalidation. - Only insert
Some(data)intoChangeSetif there is an actualCheckPointin the update chain at that height. - Only insert
NoneintoChangeSetif there is an actualCheckPointin the original chain at that height.
Suggested tests
These tests uses a data type that returns Some for prev_blockhash:
prev_blockhash invalidates
- Original:
1:A,2:B,4:D - Update:
1:A,3:C' (prev=2:B') - Result:
1:A,3:C' (prev=2:B')
Connects due to prev_blockhash
- Original:
2:B,3:C - Update:
2:B,4:D (prev=3:C) - Result:
2:B,3:C,4:D
The `chain_update` macro now constructs a `CheckPoint` instead of a `LocalChain`, enabling test cases of chain update where no point of agreement exists.
d5b2513 to
01800f4
Compare
prev_blockhash to trait ToBlockHashprev_blockhash to ToBlockHash trait
prev_blockhash to ToBlockHash traitprev_blockhash to ToBlockHash trait
- `merge_chains` is now implemented on the `LocalChain` type - `merge_chains` now errors if the update conflicts with the genesis hash. This brings the code into alignment with the documentation - Fixed faulty behavior preventing the ability to switch forks in a basic reorg scenario. The test case is modeled under the name "invalidate tip and extend chain". - Updated test expectations in `test_local_chain.rs`
The `LocalChain` maintains an invariant that a value at height 0 must be present, or the so-called "genesis" block. To prevent potentially costly lookups every time we want to retrieve the genesis hash, we store the block hash in the `LocalChain` upon construction.
Previously, methods of `CheckPoint` were constrained by `D: Copy`, and this was to prevent a possible memory leak caused by the use of `mem::forget` in the Drop impl for `CheckPoint`. Since the memory leak concern has been resolved, we relax the constraint to `Clone` instead which permits instances of `CheckPoint<D>` where `D` is not necessarily a Copy type.
`prev_blockhash` returns the hash of the previous block data, if it is known, and `None` otherwise. Implement `prev_blockhash` for `Header`, which returns the header's previous blockhash.
Fixed `CheckPoint::push` to now check that if the height being pushed directly follows the current tip height, the previous hash of `data` is the same as the current tip hash, (provided that the data's `prev_blockhash` is known). This is necessary to prevent inconsistent or invalid chains from being constructed. Fixed `insert` to displace conflicting blocks by reverting to a previous base if it's found to conflict with the inserted data. Also avoid a panic in `insert` in case `push` returns an error by returning the last successfully updated tip.
… from `extend` Now that `apply_changeset` can fail due to inconsistent data in addition to `MissingGenesisError`, we need to propagate the error rather than expect that the chain update can always succeed. Added private struct `ApplyChangeSetError`. This error is returned by `apply_changeset_to_checkpoint`. BREAKING: - Changed `LocalChain::from_blocks` to return `Result<Self, Option<CheckPoint<D>>` - Changed `LocalChain::from_changeset` to return `Result<Option<Self>, CannotConnectError)` - Changed `LocalChain::apply_changeset` to return `Result<(), CannotConnectError>`
01800f4 to
d160549
Compare
This PR adds
prev_blockhashmethod toToBlockHashtrait.prev_blockhashreturns the hash of the previous block data, if it is known, andNoneotherwise. Implementedprev_blockhashforHeader, which returns the header's previous block hash.We now require checkpoints to link via their
prev_blockhashrelation.Fixed
CheckPoint::pushto now check that if the height being pushed directly follows the current tip height, the previous hash ofdatais the same as the current tip hash, (provided that the data'sprev_blockhashis known). This is necessary to prevent inconsistent or invalid chains from being constructed.Fixed
insertto displace conflicting blocks by reverting to a previous base if it's found to conflict with the inserted data. Also avoid a panic ininsertin casepushreturns an error by returning the last successfully updated tip.In
apply_changeset{_to checkpoint}we now propagate the error fromextend. Now thatapply_changesetcan fail due to inconsistent data in addition toMissingGenesisError, we need to propagate the error rather than expect that the chain update can always succeed.fix #2095
fix #2021
Changelog notice
Changed
LocalChain::from_blocksto returnResult<Self, Option<CheckPoint<D>>LocalChain::from_changesetto returnResult<Option<Self>, CannotConnectError)LocalChain::apply_changesetto returnResult<(), CannotConnectError>Checklists
All Submissions:
New Features:
Bugfixes: