Skip to content
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

Implement linked-list LocalChain and add rpc-chain module/example #1002

Closed

Conversation

evanlinjin
Copy link
Member

@evanlinjin evanlinjin commented Jun 3, 2023

Fixes #997

Description

This PR changes the LocalChain implementation to have blocks stored as a linked-list. To verify the API of the linked-list LocalChain, the bitcoind_rpc crate and example is added (which is a block-by-block chain source). Additionally, the electrum/esplora crates and examples are also updated for the new API, but the logic is also updated to better-ensure consistency when obtaining a view of blocks.

A linked-list LocalChain allows the data-source thread to hold a shared reference to a single checkpoint and have access to the whole history of checkpoints without cloning or having a lock on LocalChain. This idea was originally proposed by @LLFourn's.

Notes to the reviewers

Sorry this is a massive PR again. The changes can be "grouped" as follows:

  • Change LocalChain API to use linked-list.
    • The apply_update method is renamed to update and the input is now the CheckPoint tip. An additional input is added (introduce_older_blocks: bool) which allows the caller to specify whether they wish to introduce new checkpoints below the highest point of agreement. This is required optimization as the update tip's chain and the original chain may share a large portion of history.
    • A _check_consistency method is added, which assert!s whether the internal tip's history is consistent with the internal index.
  • Introduce prune_and_apply_update methods for both IndexedTxGraph and Wallet. This prunes out irrelevant transactions from the TxGraph update before applying it. This will be used by block-by-block data sources as each block update is a TxGraph.
  • Update electrum crate to use the new LocalChain API. We cannot 100% guarantee consistency with electrum updates (yet), but this is an improvement.
  • Update esplora to ensure 100% consistency. LocalChain and TxGraph updates are now handled separately in ExploraExt. We make use of the block_hash field of tx statuses from the esplora API.
  • Introduce bitcoind_rpc crate and example.

Changelog notice

  • Change LocalChain API to use a linked-list of block data.
  • Add the bitcoind_rpc crate and example.
  • Fix the esplora crate to guarantee a consistent view of blocks when formulating an update.
  • Add example_rpc CLI example for bitcoind_rpc crate.
  • Fix: Remove duplicate temp-plan crate.

Checklists

All Submissions:

  • I've signed all my commits
  • I followed the contribution guidelines
  • I ran cargo fmt and cargo clippy before committing

New Features:

  • I've added tests for the new feature
  • I've added docs for the new feature

Bugfixes:

  • This pull request breaks the existing API
    * [ ] I've added tests to reproduce the issue which are now passing
  • I'm linking the issue being fixed by this PR

@evanlinjin evanlinjin changed the title WIP: Implement linked-list LocalChain WIP: Implement linked-list LocalChain Jun 3, 2023
@evanlinjin evanlinjin marked this pull request as draft June 3, 2023 19:26
@evanlinjin evanlinjin changed the title WIP: Implement linked-list LocalChain Implement linked-list LocalChain Jun 3, 2023
@evanlinjin evanlinjin force-pushed the wallet_redesign_local_chain branch 3 times, most recently from d2b2a99 to c9b648e Compare June 6, 2023 08:12
Copy link
Contributor

@LLFourn LLFourn left a comment

Choose a reason for hiding this comment

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

Initial comments. The main difference I have is that the user should not see Arc anywhere. It's an internal implementation detail.

crates/chain/src/local_chain.rs Outdated Show resolved Hide resolved
crates/chain/src/local_chain.rs Outdated Show resolved Hide resolved
crates/chain/src/local_chain.rs Outdated Show resolved Hide resolved
crates/chain/src/local_chain.rs Outdated Show resolved Hide resolved
crates/chain/src/local_chain.rs Outdated Show resolved Hide resolved
crates/chain/src/local_chain.rs Outdated Show resolved Hide resolved
crates/chain/src/local_chain.rs Outdated Show resolved Hide resolved
@evanlinjin evanlinjin force-pushed the wallet_redesign_local_chain branch 6 times, most recently from 7492685 to 9856d16 Compare June 13, 2023 09:16
@evanlinjin evanlinjin force-pushed the wallet_redesign_local_chain branch 5 times, most recently from 41ef9cd to 26cc17f Compare June 19, 2023 13:00
@evanlinjin evanlinjin changed the title Implement linked-list LocalChain Implement linked-list LocalChain and add rpc-chain module/example Jun 19, 2023
@nondiremanuel nondiremanuel added this to the 1.0.0-alpha.2 milestone Jun 20, 2023
evanlinjin and others added 24 commits July 19, 2023 16:41
This allows the data-source thread to hold a reference to checkpoints
without a lock on `LocalChain` itself.

Introduce `LocalChain::update` that replaces `determine_changeset` and
`apply_update`. This returns a closure that actually updates `self` when
called. This method allows for efficient and elegant updating while
being able to "preview" the update before applying.

The `LocalChain` update/determine_changeset tests have been updated to
also check for the final state after applying the update (not just
looking at the changeset).

Update `keychain::LocalUpdate` struct to use `CheckPoint`

Instead of containing a complete `LocalChain`, the update uses
`CheckPoint`. This simplifies the API since updating a `LocalChain` only
requires a `CheckPoint` now.

The examples and chain source `..Ext` implementations have all been
updated to use the new API.

Additionally, `..Ext` implementations didn't 100% guarantee consistency
of the updates, the logic has been changed to enforce better guarantees.
`prune_and_apply_update` first scans all txs contained in `update`
through the index, then filters out txs using `I::is_tx_relevant` before
applying the update. This is useful for block-by-block syncing.

`Wallet::apply_update` now has a second input; `prune: bool`. If `prune`
is set, irrelevant transactions of `update` will not be included.
Revert `get_or_insert` back as `insert_block`.

Method `update` now mutates `LocalChain` directly, instead of mutating
via a second call.

`CheckPoint::new_with_prev` is replaced with `CheckPoint::extend`.
Within `update()`, it is not always necessary to call `fix_links()`. The
logic to detect this was wrong previously.

Add test that would fail with the previous logic.
For esplora:

* Separate checkpoint-update logic into a separate method
  `construct_update_tip`.
* We batch-get latest blocks to ensure consistency.
* Reorg-mitigation logic is changed to only reconstruct checkpoints and
  anchors.

For electrum:

* Rename `prepare_chain_update` to `construct_update_tip`. Use
  `Client::block_headers` to get latest headers atomically (instead of
fetching headers one by one).
* `determine_tx_anchor` now uses the lowest anchor checkpoint possible.
* Add comments for better documentation.
The old logic cannot guarantee consistency of the chain history (i.e. we
may result in a `LocalChain` state which contain blocks that cannot
belong in the same chain). We also had to do a rescan if the tip has
changed since the start of the scan.

To fix this, we anchor transactions at the confirmation height and hash
(which is provided by the esplora API). We fetch the `LocalChain` update
separately, and guarantee consistency by fetching most-recent blocks
atomically via the `GET /blocks` endpoint.

Because the anchors need to be reflected in `LocalChain`,
`TxGraph::missing_blocks(&self, chain: &LocalChain)` is introduced to
fetch missing blocks in a separate call.
This logic should carry over better when we change `LocalChain` to be
monotone (bitcoindevkit#1005).
By iterating backwards over the two chains in tandem to find the
difference between them.
Local chain is a linked list whose heights are indexed in a BTreeMap.
The lower bound height is the lowest checkpoint the update chain can
introduce to the original chain.

Also add optimisation when we have a perfect connection.

Add `debug_assertions` to `LocalChain` methods that mutate. Add more
`LocalChain` unit tests.
This replaces using a lower bound height (which is a more complex API).
The introduced method is `Wallet::prune_and_apply_update`.
So we don't confuse it with `Extend::extend` (trait) that resides in
`core::iter::Extend`.
Remove the `info` field from the `BitcoindRpcUpdate::Block` variant.

`BitcoindRpcUpdate::into_update` now returns the new `CheckPoint` tip +
the `TxGraph` update (instead of `LocalUpdate`). Consequently, the
`anchor` input is also simplified.
Thank you to @LLFourn for the suggestions.

* `LocalChain::update` is now renamed back to `LocalChain::apply_update`
  and takes in a new struct `local_chain::Update` that combines a
`CheckPoint` tip and a `introduce_older_blocks: bool`. This is because
the boolean argument is determined by the chain source and not by the
caller/user.
* We've removed the premature optimization in
  `local_chain::merge_chains`.
* Deriving `PartialEq`, `Eq`, `PartialOrd`, `Ord` for `CheckPoint` does
  not make much sense. They are removed for now.
* Fix: bitcoind_rpc Cargo.toml: wrong bdk_chain version
@evanlinjin
Copy link
Member Author

Replaced by #1034

@evanlinjin evanlinjin closed this Jul 31, 2023
evanlinjin added a commit that referenced this pull request Aug 3, 2023
…crates/examples

b206a98 fix: Even more refactoring to code and documentation (志宇)
bea8e5a fix: `TxGraph::missing_blocks` logic (志宇)
db15e03 fix: improve more docs and more refactoring (志宇)
95312d4 fix: docs and some minor refactoring (志宇)
8bf7a99 Refactor `debug_assertions` checks for `LocalChain` (志宇)
315e7e0 fix: rm duplicate `bdk_tmp_plan` module (志宇)
af705da Add exclusion of example cli `*.db` files in `.gitignore` (志宇)
eabeb6c Implement linked-list `LocalChain` and update chain-src crates/examples (志宇)

Pull request description:

  Fixes #997
  Replaces #1002

  ### Description

  This PR changes the `LocalChain` implementation to have blocks stored as a linked-list. This allows the data-src thread to hold a shared ref to a single checkpoint and have access to the whole history of checkpoints without cloning or keeping a lock on `LocalChain`.

  The APIs of `bdk::Wallet`, `esplora` and `electrum` are also updated to reflect these changes. Note that the `esplora` crate is rewritten to anchor txs in the confirmation block (using the esplora API's tx status block_hash). This guarantees 100% consistency between anchor blocks and their transactions (instead of anchoring txs to the latest tip). `ExploraExt` now has separate methods for updating the `TxGraph` and `LocalChain`.

  A new method `TxGraph::missing_blocks` is introduced for finding "floating anchors" of a `TxGraph` update (given a chain).

  Additional changes:

  * `test_local_chain.rs` is refactored to make test cases easier to write. Additional tests are also added.
  * Examples are updated.
  * Exclude example-cli `*.db` files in `.gitignore`.
  * Rm duplicate `bdk_tmp_plan` module.

  ### Notes to the reviewers

  This is the smallest possible division of #1002 without resulting in PRs that do not compile. Since we have changed the API of `LocalChain`, we also need to change `esplora`, `electrum` crates and examples alongside `bdk::Wallet`.

  ### Changelog notice

  * Implement linked-list `LocalChain`. This allows the data-src thread to hold a shared ref to a single checkpoint and have access to the whole history of checkpoints without cloning or keeping a lock on `LocalChain`.
  * Rewrote `esplora` chain-src crate to anchor txs to their confirmation blocks (using esplora API's tx-status `block_hash`).

  ### Checklists

  #### All Submissions:

  * [x] I've signed all my commits
  * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)
  * [x] I ran `cargo fmt` and `cargo clippy` before committing

  #### New Features:

  * [x] I've added tests for the new feature
  * [x] I've added docs for the new feature

ACKs for top commit:
  LLFourn:
    ACK b206a98

Tree-SHA512: a513eecb4f1aae6a5c06a69854e4492961424312a75a42d74377d363b364e3d52415bc81b4aa3fbc3f369ded19bddd07ab895130ebba288e8a43e9d6186e9fcc
@notmandatory notmandatory removed this from the 1.0.0-alpha.2 milestone Oct 11, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

LocalChain should be implemented as a cheaply cloneable singly linked list
6 participants