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

btc spv: give wallet precedence for block notifications #1250

Merged
merged 8 commits into from Oct 30, 2021

Conversation

buck54321
Copy link
Member

This solves a couple of issues with bitcoin SPV syncing.

  1. Neutrino stores the block header before requesting filter headers and looking for wallet scripts, so the balanceUpdate requested by Core can come before the wallet has seen new funds. This was obvious during testnet syncing when outputs from even 1000s of blocks ago were not seen (block headers are stored in batches during initial sync) until an additional block was mined after sync.
  2. Block notifications were being sent from ExchangeWallet before the xcWallet was added to the wallets map in *Core, resulting in error messages saying non-existent 0 wallet should exist.

This solution gives wallet notifications precedence by queuing polled new tips temporarily to allow the wallet notifications to come through. This is important, because neutrino stores the block headers before requesting filters, and doesn't send the FilteredBlockConnected notification until after the filters are received and processed, allowing ample time for a poll to sneak in and grab a header whose corresponding filter hasn't been checked yet.

@buck54321 buck54321 marked this pull request as ready for review October 26, 2021 11:21
@chappjc
Copy link
Member

chappjc commented Oct 26, 2021

The btcd fix for the KnownAddress race was merged btcsuite/btcd#1763.
Wanna update the btcd require in this PR or another?

Copy link
Member

@chappjc chappjc left a comment

Choose a reason for hiding this comment

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

Looks pretty slick overall.

Comment on lines +48 to +49
type tipNotifier interface {
tipFeed() <-chan *block
Copy link
Member

Choose a reason for hiding this comment

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

If we wanted to do totally idiomatic Go, we'd make this tipFeeder I suppose, but meh.

client/asset/btc/btc.go Outdated Show resolved Hide resolved
Comment on lines +2511 to +2520
prevTip := btc.currentTip
btc.currentTip = newTip
Copy link
Member

Choose a reason for hiding this comment

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

The sameTip check in run/watchBlocks previously was good enough, but given the logic race with Stopping the queuedBlock's timer, it's possible although unlikely that the same tip could be reported twice. So maybe we either check here like:

prevTip := btc.currentTip
if prevTip.hash == newTip.hash {
	return // already reported
}
btc.currentTip = newTip

or in watchBlocks like

	case walletTip := <-walletBlock:
		if queuedBlock != nil && walletTip.height >= queuedBlock.height {
			if !queuedBlock.queue.Stop() && walletTip.hash == queuedBlock.hash {
				continue
			}
			queuedBlock = nil
		}

I think it's clearer and just as good here in reportTip.

Copy link
Member Author

Choose a reason for hiding this comment

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

I added the reportTip solution in the latest commit, but I think I'm going to go with your second one instead. If a wallet tip comes through after the queued block is sent, we'd still want to notify the user to trigger a balance check.

client/asset/btc/spv.go Outdated Show resolved Hide resolved
client/asset/btc/spv.go Show resolved Hide resolved
client/asset/btc/btc.go Outdated Show resolved Hide resolved
client/asset/btc/btc_test.go Outdated Show resolved Hide resolved
@buck54321 buck54321 force-pushed the smarttip branch 2 times, most recently from e7b10d3 to 6aa954c Compare October 28, 2021 17:36
if err != nil {
btc.log.Errorf("Error retreiving sync status before queuing polled block: %v", err)
} else if syncStatus.Syncing {
blockAllowance *= 10
Copy link
Member Author

Choose a reason for hiding this comment

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

Even with the a 10 second allowance, I was seeing warnings during testnet syncing.

When a wallet is able to provide block notifications, allow wallet notifications
to take precedence. Queue polled new tips for 10 seconds, canceling
their broadcast and sending the notification earlier if the wallet
notification comes during this window. If the wallet note doesn't
come by 10 seconds, send the queued notification and log a warning.

SPV tip updates are provided by the FilteredBlockConnected
notification, which can come after neutrino stores the block header(s).
Care is taken not to spam the caller with notifications, since they
are not limited by the polling frequency. The same mechanism for
limiting spam is used to solve the non-existent wallet error messages
for *Core though, so you get two for the price of one with that solution.
Comment on lines +2447 to +2449
if walletBlock == nil {
btc.reportNewTip(ctx, newTip)
} else {
Copy link
Member

Choose a reason for hiding this comment

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

Looks like you could continue rather than using the else indention. No difference really though.

@@ -284,9 +285,13 @@ type spvWallet struct {

log dex.Logger
loader *wallet.Loader

tipChan chan *block
syncTarget int32
Copy link
Member

Choose a reason for hiding this comment

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

Could you leave a comment about what syncTarget is? I guess it is the current highest known block number?

Comment on lines +483 to +488
if atomic.SwapInt32(&w.syncTarget, target) == 0 && target > 0 {
w.tipChan <- &block{
hash: blk.Hash,
height: int64(blk.Height),
}
}
Copy link
Member

Choose a reason for hiding this comment

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

// Setting highest known block number if not initiated?

Comment on lines +966 to +968
if syncTarget == 0 || (lastBlock.Height < syncTarget && lastBlock.Height%10_000 != 0) {
continue
}
Copy link
Member

Choose a reason for hiding this comment

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

what does lastBlock.Height%10_000 != 0 do? this will be true at at 5001 - 9999 and then from 10001?

Copy link
Member

@chappjc chappjc left a comment

Choose a reason for hiding this comment

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

This is working very well now. I'm not seeing any delays or unfound transactions. Just one final request for a log when AttachedBlocks come in a ntfn.

if syncTarget == 0 || (lastBlock.Height < syncTarget && lastBlock.Height%10_000 != 0) {
continue
}

Copy link
Member

Choose a reason for hiding this comment

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

Could I please get the following here?

for ib := range note.AttachedBlocks {
	for _, nt := range note.AttachedBlocks[ib].Transactions {
		w.log.Debugf("Block %d contains wallet transaction %v", note.AttachedBlocks[ib].Height, nt.Hash)
	}
}

@chappjc chappjc merged commit 2d18cc8 into decred:master Oct 30, 2021
@chappjc
Copy link
Member

chappjc commented Nov 2, 2021

2\. Block notifications were being sent from `ExchangeWallet` before the `xcWallet` was added to the `wallets` map in `*Core`, resulting in error messages saying `non-existent 0 wallet should exist`.

Which change in this PR was to address this? I don't think that's resolved. I'm working on trade_simnet_test.go, and I get that error message, but I don't see a change to how wallets map is updated w.r.t. start up of ExchangeWallet.

@buck54321
Copy link
Member Author

2\. Block notifications were being sent from `ExchangeWallet` before the `xcWallet` was added to the `wallets` map in `*Core`, resulting in error messages saying `non-existent 0 wallet should exist`.

Which change in this PR was to address this? I don't think that's resolved. I'm working on trade_simnet_test.go, and I get that error message, but I don't see a change to how wallets map is updated w.r.t. start up of ExchangeWallet.

https://github.com/buck54321/dcrdex/blob/a631acc3851a10682debdd0a0609081de33d1200/client/asset/btc/spv.go#L965-L975

syncTarget is zero until syncStatus is called. But, syncStatus can also be called from the block monitoring loop., which I guess could cause it to be set early. Hmm.

@chappjc chappjc added this to the 0.4 milestone Nov 24, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants