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

chore(btcclient): use notifier interface #20

Merged
merged 14 commits into from
Aug 27, 2024
Merged

chore(btcclient): use notifier interface #20

merged 14 commits into from
Aug 27, 2024

Conversation

Lazar955
Copy link
Member

  • Removes usage of btc client with subscription to zmq
  • Removes proprietary client implementation of zmq subscription as we now rely on lnd's

References issue

@Lazar955 Lazar955 requested a review from KonradStaniec August 23, 2024 13:09
@Lazar955 Lazar955 marked this pull request as ready for review August 23, 2024 13:09
@RafilxTenfen
Copy link
Contributor

Can we remove

func (c *Client) DumpPrivKey(address btcutil.Address) (*btcutil.WIF, error) {

func (c *Client) DumpPrivKey(address btcutil.Address) (*btcutil.WIF, error) {
	return c.Client.DumpPrivKey(address)
}

@Lazar955
Copy link
Member Author

Can we remove

func (c *Client) DumpPrivKey(address btcutil.Address) (*btcutil.WIF, error) {

func (c *Client) DumpPrivKey(address btcutil.Address) (*btcutil.WIF, error) {
	return c.Client.DumpPrivKey(address)
}

Yep, I've opened an issue to cleanup btc client and btc wallet interaces, they can be merged. So will be done in a separate PR

Copy link
Contributor

@RafilxTenfen RafilxTenfen left a comment

Choose a reason for hiding this comment

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

LGTM, tested the vigilante with bitcoind on deployments/local and was able to have healthy BTC delegations again 🎉

Copy link
Member

@gitferry gitferry left a comment

Choose a reason for hiding this comment

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

Nice work! Generally, different vigilante programs share a lot of code. So one point of the refactoring is to extract common parts into a libary (e.g., lnd notifier) and reduce duplicate code as much as possible. This PR is a good step heading this direction. We can continue this in future prs

if err != nil {
panic(fmt.Errorf("failed to get BTC net params: %w", err))
}
btcCfg := btcclient.CfgToBtcNodeBackendConfig(cfg.BTC, "")
Copy link
Member

Choose a reason for hiding this comment

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

rawCert is needed for btcd, which we wanted to remove, correct?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yep, there's a new issue to remove btcd from repo (previously merged PR regarding that was to use it in e2e)

Comment on lines 66 to 75
// create the chain notifier
btcParams, err := netparams.GetBTCParams(cfg.BTC.NetParams)
if err != nil {
panic(fmt.Errorf("failed to get BTC net params: %w", err))
}
btcCfg := btcclient.CfgToBtcNodeBackendConfig(cfg.BTC, "")
btcNotifier, err := btcclient.NewNodeBackend(btcCfg, btcParams, &btcclient.EmptyHintCache{})
if err != nil {
panic(fmt.Errorf("failed to initialize notifier: %w", err))
}
Copy link
Member

Choose a reason for hiding this comment

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

This chunk is duplicated across several programs. Maybe we can wrap it into a general constructor?

Comment on lines 13 to 24
if err := bs.btcNotifier.Start(); err != nil {
bs.logger.Errorf("Failed starting notifier")
return
}

blockNotifier, err := bs.btcNotifier.RegisterBlockEpochNtfn(nil)
if err != nil {
bs.logger.Errorf("Failed registering block epoch notifier")
return
}

defer blockNotifier.Cancel()
Copy link
Member

Choose a reason for hiding this comment

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

Maybe it's better to ducouple the start of the notifier and the block handler by passing the block channel to the handler. See

go uw.handleNewBlocks(blockEventNotifier)

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yup I would start notifier in start function

Comment on lines 14 to 25
if err := r.btcNotifier.Start(); err != nil {
r.logger.Errorf("Failed starting notifier")
return
}

blockNotifier, err := r.btcNotifier.RegisterBlockEpochNtfn(nil)
if err != nil {
r.logger.Errorf("Failed registering block epoch notifier")
return
}

defer blockNotifier.Cancel()
Copy link
Member

Choose a reason for hiding this comment

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

Same here

Comment on lines 35 to 51
tip := r.btcCache.Tip()

// Determine if a reorg happened to we know which flow to continue
// if the new block has the same height but a different hash.
reorg := false
if tip != nil {
if epoch.Height < tip.Height ||
(epoch.Height == tip.Height && epoch.BlockHeader.BlockHash() != tip.Header.BlockHash()) {
reorg = true
}
}

var errorRequiringBootstrap error
if event.EventType == types.BlockConnected {
errorRequiringBootstrap = r.handleConnectedBlocks(event)
} else if event.EventType == types.BlockDisconnected {
errorRequiringBootstrap = r.handleDisconnectedBlocks(event)
if !reorg {
errorRequiringBootstrap = r.handleConnectedBlocks(epoch.Height, epoch.BlockHeader)
} else {
errorRequiringBootstrap = r.handleDisconnectedBlocks(epoch.BlockHeader)
Copy link
Member

Choose a reason for hiding this comment

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

It seems that we do the same chaining check (from L35 to L45) in both handleDisconnectedBlocks and handleDisconnectedBlocks. I believe we can combine the logic in one handleNewBlock() method. Basically, if the new block cannot connect to the cache tip, we bootstrap. Otherwise, we add it to the cache, no needing of separate logic

Copy link
Collaborator

Choose a reason for hiding this comment

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

oh that this good idea 👍

My initial thought was that we could, translate new block events into connected / disconnected block events i.e
If we have the chain:
H1 -> H2 -> H3 , now the the reorg happens and the chain becomes H1 - H2' - H3', and now we receive H3' then we can:

  1. Find the common ancestor between forks i.e H1
  2. Calculate that H2 and H3 were removed, so send to disconnection events starting from the top
  3. Now send connection events for H2 and H3

Bootstrapping after reorg is a bit brutal i.e it is costly operation, but given that re-orgs are pretty rare imo it is possible route to take and if we encounter some problems with it we can always go for more fancy approach.

refactor notifier init

merge reporter block handles
@Lazar955 Lazar955 requested a review from gitferry August 26, 2024 13:40
Copy link
Member

@gitferry gitferry left a comment

Choose a reason for hiding this comment

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

Much cleaner! One last concern is whether the btc scanner can output a canonical chain in expected sequence after bootstraping and event handler switch. It would be good to have a test to cover the cases where bootstraping -> block handler, block handler -> reorg happens -> bootstraping -> block handler. Currently we only have test for the bootstraping case. The tests can be done in this or separate pr

btcNotifier, err := btcclient.NewNodeBackend(btcCfg, btcParams, &btcclient.EmptyHintCache{})
if err != nil {
panic(fmt.Errorf("failed to create btc chain notifier: %w", err))
panic(err)
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
panic(err)
panic(fmt.Errorf("failed to create btc chain notifier: %w", err))

Copy link
Member Author

Choose a reason for hiding this comment

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

errors are already wrapped in the NewNodeBackendWithParams

Comment on lines 73 to 74
// failing to request the block, which means a bug
panic(err)
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// failing to request the block, which means a bug
panic(err)
// failing to request the block
panic(fmt.Errorf("failed to request block by hash: %s", blockHash.String))

Copy link
Member

Choose a reason for hiding this comment

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

This could also be network issue

Copy link
Member Author

Choose a reason for hiding this comment

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

good spot, typo in suggestion

Comment on lines 92 to 110
go bs.Bootstrap()

bs.BtcClient.MustSubscribeBlocks()

bs.Started.Store(true)
bs.logger.Info("the BTC scanner is started")

if err := bs.btcNotifier.Start(); err != nil {
bs.logger.Errorf("Failed starting notifier")
return
}

blockNotifier, err := bs.btcNotifier.RegisterBlockEpochNtfn(nil)
if err != nil {
bs.logger.Errorf("Failed registering block epoch notifier")
return
}

// start handling new blocks
bs.wg.Add(1)
go bs.blockEventHandler()
go bs.blockEventHandler(blockNotifier)
Copy link
Member

Choose a reason for hiding this comment

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

I was worried about possible racing issue between the Bootstrap and blockEventHandler threads. They both produce confirmed blocks to the same channel, so they need to be sequencial. I can think of two options:

  1. keep them as two separate go routines and use the Synced controller to ensure blockEventHandler happens after Bootstrap
  2. combine them into a single goroutine and when creating the notifier

I'm in favor of option (2) in which we probably can remove the Synced controller. Nevertheless, after bootstrapping, we should be able to get the best known block and put it in btcNotifier.RegisterBlockEpochNtfn(bestKnownBlock), so that we don't duplicate the chain tip

return
}

blockNotifier, err := r.btcNotifier.RegisterBlockEpochNtfn(nil)
Copy link
Member

Choose a reason for hiding this comment

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

Can we get the best known block after bootstrapping and set it as the parameter? Otherwise, the notifier will immediately get the chain tip which could be duplicate with the one processed during bootstrap

@Lazar955 Lazar955 requested a review from gitferry August 27, 2024 08:40
Copy link
Member

@gitferry gitferry left a comment

Choose a reason for hiding this comment

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

Thanks for all the changes. Lgtm! It would be nice to have comprehensive tests about boostraping and normal block events but can be done in a separate pr

blockHash := header.BlockHash()
ib, _, err := bs.BtcClient.GetBlockByHash(&blockHash)
if err != nil {
// failing to request the block, which means a bug
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// failing to request the block, which means a bug
// failing to request the block

@Lazar955
Copy link
Member Author

Lazar955 commented Aug 27, 2024

@gitferry

Thanks, created a new issue for that

@Lazar955 Lazar955 merged commit e992231 into dev Aug 27, 2024
8 checks passed
@Lazar955 Lazar955 deleted the lazar/use-notifier branch August 27, 2024 13:22
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.

4 participants