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

bdk + esplora not seeing an old transaction as confirmed #1151

Closed
danielabrozzoni opened this issue Oct 6, 2023 · 8 comments · Fixed by #1152
Closed

bdk + esplora not seeing an old transaction as confirmed #1151

danielabrozzoni opened this issue Oct 6, 2023 · 8 comments · Fixed by #1152
Assignees
Labels
bug Something isn't working

Comments

@danielabrozzoni
Copy link
Member

danielabrozzoni commented Oct 6, 2023

Describe the bug
When syncing using bdk and esplora, we might not see that a certain tx is confirmed, and consider it unconfirmed forever

To Reproduce
See https://github.com/sebastianmontero/bdk-esplora-test

Expected behavior
bdk correctly seeing a transaction as confirmed when it is

Build environment

@danielabrozzoni danielabrozzoni added the bug Something isn't working label Oct 6, 2023
@danielabrozzoni danielabrozzoni self-assigned this Oct 6, 2023
@danielabrozzoni danielabrozzoni moved this to In Progress in BDK Oct 6, 2023
danielabrozzoni added a commit to danielabrozzoni/bdk that referenced this issue Oct 6, 2023
...before using missing_heights

Fixes bitcoindevkit#1151.
When wallet_esplora_* was used to sync a wallet containing a transaction
confirmed some time ago (more than 10-15 blocks ago), the transaction would
be stuck in an "unconfirmed" state forever.

At the first scan time, `update_local_chain` would just fetch the last 10 to
15 blocks (depending on the server used), and `tx_graph.missing_heights`
wouldn't return the tx's confirmation block as it was called on the
original, non-updated tx_graph.
So, after the first scan, we would have a transaction in memory with an
anchor that doesn't exist in our local_chain, and try_get_chain_position
would return unconfirmed.

When scanning again, missing_heights would find the missing anchor, but
`update_local_chain` wouldn't include it as it's older than
ASSUME_FINAL_DEPTH.

The missing block would be downloaded every time, but never included in
the local_chain, and the transaction would remain unconfirmed forever.

Here we update the tx_graph immediately after getting the graph update,
so that `missing_heights` can correctly return the anchor height, and
`update_local_chain` fetch it and include it in the chain.
@LLFourn
Copy link
Contributor

LLFourn commented Oct 9, 2023

The bug is in the above code and in the wallet example. You're meant to ask the update what heights are missing from the current chain not the wallet. Though I'm not sure why it would be unconfirmed forever. The transaction should be added with its anchors anyway so it should be missing the next time I would have thought. Can we confirm why the anchor is not returned from missing_heights in the future? Is it never inserted? If it's there and not returned it's probably a bug in missing_heights.

@danielabrozzoni
Copy link
Member Author

@LLFourn missing_heights would return the anchor, but update_local_chain wouldn't insert it as it's older than ASSUME_FINAL_DEPTH:

let updated_hash = match fetched_blocks.entry(local_block.height) {
btree_map::Entry::Occupied(entry) => *entry.get(),
btree_map::Entry::Vacant(entry) => *entry.insert(
if local_tip_height - local_block.height >= ASSUME_FINAL_DEPTH {
local_block.hash
} else {
self.get_block_hash(local_block.height)?

danielabrozzoni added a commit to danielabrozzoni/bdk that referenced this issue Oct 9, 2023
...graph update

Fixes bitcoindevkit#1151.
When wallet_esplora_* was used to sync a wallet containing a transaction
confirmed some time ago (more than 10-15 blocks ago), the transaction would
be stuck in an "unconfirmed" state forever.

At the first scan time, `update_local_chain` would just fetch the last 10 to
15 blocks (depending on the server used), and `tx_graph.missing_heights`
wouldn't return the tx's confirmation block as it was called on the
original, non-updated tx_graph.
So, after the first scan, we would have a transaction in memory with an
anchor that doesn't exist in our local_chain, and try_get_chain_position
would return unconfirmed.

When scanning again, missing_heights would find the missing anchor, but
`update_local_chain` wouldn't include it as it's older than
ASSUME_FINAL_DEPTH.

The missing block would be downloaded every time, but never included in
the local_chain, and the transaction would remain unconfirmed forever.

Here we call missing_heights on the updated graph, so that it can
correctly return the anchor height, and `update_local_chain` can
fetch it and include it in the chain.
evanlinjin added a commit that referenced this issue Oct 9, 2023
b1461f0 fix(wallet_esplora): missing_heights uses the... ...graph update (Daniela Brozzoni)

Pull request description:

  Fixes #1151.

  When wallet_esplora_* was used to sync a wallet containing a transaction confirmed some time ago (more than 10-15 blocks ago), the transaction would be stuck in an "unconfirmed" state forever.

  At the first scan time, `update_local_chain` would just fetch the last 10 to 15 blocks (depending on the server used), and `tx_graph.missing_heights` wouldn't return the tx's confirmation block as it was called on the original, non-updated tx_graph.
  So, after the first scan, we would have a transaction in memory with an anchor that doesn't exist in our local_chain, and try_get_chain_position would return unconfirmed.

  When scanning again, missing_heights would find the missing anchor, but `update_local_chain` wouldn't include it as it's older than ASSUME_FINAL_DEPTH.

  The missing block would be downloaded every time, but never included in the local_chain, and the transaction would remain unconfirmed forever.

  Here we call missing_heights on the updated graph, so that it can correctly return the anchor height, and `update_local_chain` can fetch it and include it in the chain.

  ### Notes to the reviewers

  I'm not sure if this is the right approach, so I'm opening this PR to gather feedback. I still need to add tests, I'll do so once we decide if this is the right way to go or not.

  ### 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

  #### Bugfixes:

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

ACKs for top commit:
  evanlinjin:
    ACK b1461f0

Tree-SHA512: ba0cf85929644ee737dbc77e6afec662845532de0f120917aa6000ca8f5db79d0cb3971bd92285b5c5b5d26042b60b6c8536f50c9bd49615e31f5da28e80a509
@github-project-automation github-project-automation bot moved this from In Progress to Done in BDK Oct 9, 2023
@LLFourn
Copy link
Contributor

LLFourn commented Oct 10, 2023

@LLFourn missing_heights would return the anchor, but update_local_chain wouldn't insert it as it's older than ASSUME_FINAL_DEPTH:

let updated_hash = match fetched_blocks.entry(local_block.height) {
btree_map::Entry::Occupied(entry) => *entry.get(),
btree_map::Entry::Vacant(entry) => *entry.insert(
if local_tip_height - local_block.height >= ASSUME_FINAL_DEPTH {
local_block.hash
} else {
self.get_block_hash(local_block.height)?

But it should be part of request_heights is what I mean. The things in request_heights are always fetched:

for height in request_heights {
// do not fetch blocks higher than remote tip
if height > new_tip_height {
continue;
}
// only fetch what is missing
if let btree_map::Entry::Vacant(entry) = fetched_blocks.entry(height) {
let hash = self.get_block_hash(height)?;
entry.insert(hash);
}
}

So more succinct question why isn't the things in missing_heights going into request_heights and then being returned.
Maybe something going wrong here:

fetched_blocks
// we exclude anything at or below the first cp of the update chain otherwise
// building the chain will fail
.split_off(&(first_cp.height() + 1))
.into_iter()
.map(|(height, hash)| BlockId { height, hash })
.fold(first_cp, |prev_cp, block| {
prev_cp.push(block).expect("must extend checkpoint")
})

@danielabrozzoni
Copy link
Member Author

danielabrozzoni commented Oct 10, 2023 via email

@LLFourn
Copy link
Contributor

LLFourn commented Oct 10, 2023

This seems like a bug if so. Thanks a lot for looking into it. I'll just reopen this because I think although this is not the main problem it should have fixed itself. FWIW I don't understand why we are doing a split_off which seems like it might be the culprit.

cc @evanlinjin

@LLFourn LLFourn reopened this Oct 10, 2023
@evanlinjin
Copy link
Member

evanlinjin commented Oct 10, 2023

https://github.com/sebastianmontero/bdk-esplora-test/blob/master/src/main.rs#L34

Would this line be the culprit?

Edit: okay this is already bought up, I didn't read the above conversation.

@nondiremanuel
Copy link

I close this issue since it was fixed by #1152

@danielabrozzoni
Copy link
Member Author

Fair, I opened #1199 to keep track of the bug we discovered while investigating this one

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
Archived in project
Development

Successfully merging a pull request may close this issue.

4 participants