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

client/asset/btc: fix SPV reorg handling #2225

Merged
merged 5 commits into from Mar 20, 2023

Conversation

norwnd
Copy link
Contributor

@norwnd norwnd commented Mar 15, 2023

This PR targeting BTC SPV wallet being able to spot reorgs, as per #2219 (review) (functionally mirroring #2219).

While it is possible to just fix the issue and move on I think some code clean up is warranted here.

client/asset/btc/btc.go Outdated Show resolved Hide resolved
@norwnd
Copy link
Contributor Author

norwnd commented Mar 15, 2023

Will also probably want to add more unit tests (similar to #2219) here, but existing ones are at least passing.

@chappjc
Copy link
Member

chappjc commented Mar 15, 2023

Thanks.

  • you may squash the fixup commit for blockheight
  • the find redemption changes should have been a separate commit

@norwnd norwnd force-pushed the client-asset-btc-spv-reorg-handling branch from 52c8b0d to 718406b Compare March 15, 2023 15:52
@chappjc chappjc changed the title client/asset/btc: SPV reorg handling client/asset/btc: fix SPV reorg handling Mar 15, 2023
@norwnd
Copy link
Contributor Author

norwnd commented Mar 15, 2023

you may squash the fixup commit for blockheight

Yep, just squashed, originally decided to do a commit on top in case somebody was already reviewing the 1st commit.

@chappjc chappjc added this to the 0.6 milestone Mar 15, 2023
@chappjc
Copy link
Member

chappjc commented Mar 15, 2023

Conditionally tagging this for 0.6, but again I would prefer the unrelated find redemption changes in a separate commit. It needs discussion.

@norwnd
Copy link
Contributor Author

norwnd commented Mar 16, 2023

but again I would prefer the unrelated find redemption changes in a separate commit. It needs discussion.

Okay, turned out simple enough to do, moved redemptions search refactor here with that last force-push.

Comment on lines -1050 to +1068
Hash: hdr.BlockHash().String(),
Confirmations: int64(confirms(blockHeight, tip.Height)),
Height: int64(blockHeight),
Time: hdr.Timestamp.Unix(),
}, nil
Hash: hdr.BlockHash().String(),
Confirmations: confirmations,
Height: int64(blockHeight),
Time: hdr.Timestamp.Unix(),
PreviousBlockHash: hdr.PrevBlock.String(),
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Looks like without initialising PreviousBlockHash here we won't be able to to crawl back the chain to find common ancestor with mainchain (instead, we'd nil pointer dereference).

client/asset/btc/spv_wrapper.go Outdated Show resolved Hide resolved
Comment on lines 1059 to 1061
if tip.Height < blockHeight { // if tip is less, may be rolling back, so just mock dcrd/dcrwallet
confirmations = 0
}
Copy link
Member

Choose a reason for hiding this comment

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

Even if it's not mainchain? If it's mainchain and tip.Height < blockHeight, confirmations would be set to 0 by the confirms fn.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've re-checked with #2219 once again, there is slight discrepancy which should be addressed by 3ded904, thanks for pointing it out!

If it's mainchain and tip.Height < blockHeight, confirmations would be set to 0 by the confirms fn.

I believe when we have tip.Height < blockHeight we can't also have mainchain=true, right ? So this shouldn't be an issue. But to be absolutely sure I've changed this condition a bit to only trigger on mainchain=false.

Copy link
Member

Choose a reason for hiding this comment

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

I've changed this condition a bit to only trigger on mainchain=false.

I think @itswisdomagain's point was that this tip.Height < blockHeight check is not required at all because the confirms func handles this, unlike with dcr where we don't have that helper. If blockIsMainchain says false, I don't believe we would overrule that.

In other words, with no knowledge about confirms functionality in this < situation, the code would be:

	if mainchain {
		if tip.Height < blockHeight { // if tip is less, may be rolling back, so just mock dcrd/dcrwallet
			confirmations = 0
		} else {	
			confirmations = int64(confirms(blockHeight, tip.Height))
		}
	}

	return &blockHeader{

But confirms handles that already, so it's just

	if mainchain {
		confirmations = int64(confirms(blockHeight, tip.Height))
	}

	return &blockHeader{

func (w *spvWallet) getBlockHeader(blockHash *chainhash.Hash) (*blockHeader, error) {
// getBlockHeader gets the *blockHeader for the specified block hash. It also
// returns a bool value to indicate whether this block is a part of main chain.
// For orphaned blocks header.Confirmations is negative (typically -1).
Copy link
Member

Choose a reason for hiding this comment

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

The "typically" wording only applies to the RPC code.

Comment on lines 1059 to 1061
if tip.Height < blockHeight { // if tip is less, may be rolling back, so just mock dcrd/dcrwallet
confirmations = 0
}
Copy link
Member

Choose a reason for hiding this comment

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

I've changed this condition a bit to only trigger on mainchain=false.

I think @itswisdomagain's point was that this tip.Height < blockHeight check is not required at all because the confirms func handles this, unlike with dcr where we don't have that helper. If blockIsMainchain says false, I don't believe we would overrule that.

In other words, with no knowledge about confirms functionality in this < situation, the code would be:

	if mainchain {
		if tip.Height < blockHeight { // if tip is less, may be rolling back, so just mock dcrd/dcrwallet
			confirmations = 0
		} else {	
			confirmations = int64(confirms(blockHeight, tip.Height))
		}
	}

	return &blockHeader{

But confirms handles that already, so it's just

	if mainchain {
		confirmations = int64(confirms(blockHeight, tip.Height))
	}

	return &blockHeader{

Comment on lines 1765 to 1770
func confirms(txHeight, curHeight int32) uint32 {
switch {
case txHeight == -1, txHeight > curHeight:
return 0
case curHeight >= txHeight:
return uint32(curHeight - txHeight + 1) // positive numbers here, no overflow
default:
return curHeight - txHeight + 1
return 0 // undefined for all other cases
Copy link
Member

@chappjc chappjc Mar 17, 2023

Choose a reason for hiding this comment

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

One thing we're missing with these changes to confirms is special handling when txHeight is -1. When this helper is used with txHeight, _ := w.cl.GetBlockHeight(blockHash) as in our getBlockHeader method, I don't think we'd ever get -1, however this helper with wtxmgr.TxDetails types like txDetails.Block.Height where -1 is expected in cases and we don't want to change any behavior there e.g. getTxOut.

Note that this confirms helper is lifted directly from dcrwallet: https://github.com/decred/dcrwallet/blob/0aeb02cde83ebb552b802b4eebcc151fe371325c/wallet/wallet.go#L4446-L4456

Let's revert the changes to this function please.

@norwnd norwnd force-pushed the client-asset-btc-spv-reorg-handling branch from d792bff to 5e1ceb8 Compare March 18, 2023 05:24
@chappjc chappjc merged commit 2822708 into decred:master Mar 20, 2023
5 checks passed
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

4 participants