-
Notifications
You must be signed in to change notification settings - Fork 2.1k
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
Remove txindex requirement for full nodes #751
Conversation
Looks like to fully support pruned nodes, we'll also have to make some changes in the @Roasbeef, should I open a new PR to address this? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Excellent work on this one! I have reviewed the bitcoind part of this PR, and things are looking very good! 👍
My biggest code is that the massive code duplication that is being added to these files will make them very hard to maintain going forward. With the current way the notifier structs are set up, I can understand it is hard to extract out common code, but I think this PR would be an excellent opportunity to figure out how to refactor this. One way to go about this would be to use this PR to figure out a good way to do the refactoring, then push the refactoring as a separate PR (that does not change behaviour, only pure refactor), before rebasing this PR on top.
|
||
// netToParams maps the Bitcoin network specific magic number to its | ||
// parameters. | ||
netToParams = map[wire.BitcoinNet]*chaincfg.Params{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Slightly easier would be to store the params
used when creating the notifier in the BitcoindNotifier
struct.
txOut, err := b.chainConn.GetTxOut(&op.Hash, | ||
op.Index, true) | ||
if err != nil { | ||
chainntnfs.Log.Error(err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you want to continue
here?
|
||
// historicalSpendDetails retrieves the spend details for an already spent | ||
// outpoint and dispatches its spend notification. | ||
func (b *BitcoindNotifier) historicalSpendDetails(outpoint *wire.OutPoint, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Naming suggestion: notifyHistoricalSpendDetails
to indicate this will notify clients.
err = b.historicalSpendDetails(&op, | ||
msg.heightHint, bestHeight) | ||
if err != nil { | ||
chainntnfs.Log.Error(err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
continue
?
chainntnfs/btcdnotify/btcd.go
Outdated
close(ntfn.spendChan) | ||
} | ||
delete(b.spendNotifications, prevOut) | ||
b.dispatchSpendNotification(prevOut, spendDetails) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here you should either pass the clients
instead of the outpoint, or remove the if _, ok := b.spendNotifications[prevOut]; ok {
check above.
chainntnfs/btcdnotify/btcd.go
Outdated
@@ -3,20 +3,24 @@ package btcdnotify | |||
import ( | |||
"errors" | |||
"fmt" | |||
"net" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this commit is getting quite big, maybe an idea to split it into two? :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not gonna review this file for now, as I reckon it's mostly the same changes as for bitcoind
. We should really try to dedup some of the code, if possible.
chainntnfs/btcdnotify/btcd.go
Outdated
|
||
// netToParams maps the Bitcoin network specific magic number to its | ||
// parameters. | ||
netToParams = map[wire.BitcoinNet]*chaincfg.Params{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same
chainntnfs/btcdnotify/btcd.go
Outdated
// Begin looking for the spending transaction by fetching blocks | ||
// from our peer at every height and scanning them. | ||
for height := lastHeight; height <= bestHeight; height++ { | ||
blockHash, err := b.chainConn.GetBlockHash(int64(height)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure how easy it will be, but could we extract common code by putting the b.chainConn
behind an interface that could be used by both btcd
and `bitcoind``
chainntnfs/btcdnotify/btcd_test.go
Outdated
|
||
// testConfDetailsFromTxIndex tests that we can find the transaction from the | ||
// node's transaction index and that the details retrieved are correct. | ||
func testConfDetailsFromTxIndex(t *testing.T, miner *rpctest.Harness, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are these similar to those for bitcoind
? Possible to put the two notifiers behind a common interface, and reuse the tests?
chainntnfs/interface_test.go
Outdated
"github.com/roasbeef/btcd/btcec" | ||
"github.com/roasbeef/btcd/btcjson" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why was this file changed?
Thanks for the review @halseth! This PR has been modified to provide manually scanning the backend's chain as the only fallback method when the transaction index is not enabled. Before attempting to include support for pruned nodes, the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice work!
Before doing any more work to implement a pruned mode, or the like, think we'll need to aggressively refactor this package as a whole, in order to get rid of all the duplicated code across the various backend implementations.
Also I commented in-line within the PR w.r.t a possible danger when attempting to dispatch confirmations due to an incorrect height hint being passed by the caller. This PR mostly read swell to me, but we'll need to ensure that we're not introducing new bugs for nodes that are running without the txindex.
if err != nil { | ||
chainntnfs.Log.Error(err) | ||
} | ||
b.heightMtx.RLock() | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is the mutex around access to the txConfNotifier
no longer needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The mutex is only being used to guard the ChainNotifier
's bestHeight
. Calling txConfNotifier.Register
without it should be fine since it doesn't have an effect on the bestHeight
. I'm guessing it was needed at some point and it didn't get removed. I could be wrong though.
) (*chainntnfs.TxConfirmation, error) { | ||
|
||
// If the transaction already has some or all of the confirmations, | ||
// then we may be able to dispatch it immediately. | ||
// TODO: fall back to scanning blocks if txindex isn't on. | ||
tx, err := b.chainConn.GetRawTransactionVerbose(txid) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The prior comment, minus the TOOD which will now be fixed, can likely remain.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
} | ||
|
||
// As we need to fully populate the returned TxConfirmation struct, | ||
// grab the block in which the transaction was confirmed so we can | ||
// locate its exact index within the block. | ||
blockHash, err := chainhash.NewHashFromStr(tx.BlockHash) | ||
if err != nil { | ||
return nil, fmt.Errorf("unable to get block hash %v for historical "+ | ||
"dispatch: %v", tx.BlockHash, err) | ||
return nil, err |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why was the wrapped error (which had more detail) removed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
if txHash == targetTxidStr { | ||
txIndex = i | ||
break | ||
return &chainntnfs.TxConfirmation{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 on the simplification here
// earliest height the transaction could have been included in, to the current | ||
// height in the chain. If it is found, the confirmation details are returned. | ||
// Otherwise, an error is not returned. | ||
func (b *BitcoindNotifier) confDetailsManually(txid *chainhash.Hash, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With this change, we'll need to do a clean sweep through the entire codebase to ensure that heightHint
is set properly. There are some known cases with neutrino (particularly in the contractcourt
) where an incorrect height hint value can cause us to actually miss a notification all together. We'll need to extensively test this patch to ensure these holes are patched up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was running this patch on testnet for a bit and didn't run into any issues. I'll continue testing and see if I can run into any more.
return nil, err | ||
} | ||
} else { | ||
// Otherwise, we'll rely on manually scanning our chain |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why's this block necessary? Instead we can just query for the block hash of the height hint. Then the block below can be simplified to simply call into Rescan
with the proper block hash.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, will fix.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
chainntnfs/btcdnotify/btcd.go
Outdated
return nil, err | ||
} | ||
|
||
targetTxidStr := outpoint.Hash.String() | ||
out: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar comment here w.r.t this block not being necessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
// First, we'll attempt to retrieve the transaction details using the | ||
// backend node's transaction index. | ||
txConf, err := b.confDetailsFromTxIndex(txid) | ||
if err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Noticed this while addressing your comments: maybe we should proceed to use the heightHint
if this fails, rather than returning the error?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With the new code, the nature of the error is likely critical. We'll need to properly test with both bitcoind
and btcd
to ensure that our logic catches the "no tx index" error properly.
Can you rebase this to master? Thanks! |
16244be
to
7e7c5ef
Compare
Rebased. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Needs YAR (yet-another-rebase) due to some changes that recently landed to make mempool spends, but afterwards
LGTM ⛄️
chainntnfs/btcdnotify/btcd.go
Outdated
"with height %d", height) | ||
} | ||
|
||
block, err := b.chainConn.GetBlockVerbose(blockHash) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Worth adding a TODO
here that we can actually just fetch the neutrino filters here. Will save us a bit of processing as we can quickly ascertain if a transaction maybe includes a block or not.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
When we (eventually) port over the integration tests to run on all backends, we should ensure that we're able to start and properly execute the tests against instances of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change looks good to me now, only a nit about a confusing comment 💯
The backend logic is becoming very similar for btcd
and bitcoind
now (which is good), maybe add a TODO to extract common code?
|
||
// confDetailsFromTxIndex looks up whether a transaction is already included | ||
// in a block in the active chain by using the backend node's transaction index. | ||
// If it is found, the confirmation details are returned. Otherwise, an error |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comment describing if/when an error is returned is confusing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
return nil, fmt.Errorf("unable to query for txid(%v): %v", txid, err) | ||
} | ||
|
||
// Make sure we actually retrieved a transaction that is included in a |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thumbsup on this refactoring!
// block in the active chain by scanning the chain's blocks, starting from the | ||
// earliest height the transaction could have been included in, to the current | ||
// height in the chain. If it is found, the confirmation details are returned. | ||
// Otherwise, an error is not returned. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same. I think I understand what this means, but it's a bit confusing 😛
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
Rebased and added TODOs. |
// confDetailsFromTxIndex looks up whether a transaction is already included | ||
// in a block in the active chain by using the backend node's transaction index. | ||
// If the transaction is found, its confirmation details are returned. | ||
// Otherwise, a nil error is returned. A non-nil error may also be returned if |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion:
"If the transaction is found in the index, its confirmation details are returned. If it is not found in the index, nil is returned."
I think that should make it more clear that a non-nil
error is critical(?).
Before this commit, we relied on the need of full nodes to enable the transaction index. This allowed us to fetch historical details about transactions in order to register and dispatch confirmation and spend notifications. This commit allows us to drop that requirement by providing a fallback method to use when the transaction index is not enabled. This fallback method relies on manually scanning blocks for the transactions requested, starting from the earliest height the transactions could have been included in, to the current height in the chain.
Now that we have a fallback method for when the transaction index is not enabled, its requirement to be enabled for a full node is no longer needed, so we can safely remove this check.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM 🔥
Will make an issue for a clean sweep to ensure height hints are always properly set. Last I investigated, there were some areas in the utxo nursery, and also the |
Fixes #527.
So far,
lnd
has required its backendbtcd
andbitcoind
full nodes to enable the transaction index. It is used to retrieve details for transaction confirmations and/or already spent transactions. This PR aims to remove that requirement by introducing some fallback methods, in the event that the transaction index is not enabled.When attempting to retrieve these details, we now have the following workflow:
3. In the case of pruned nodes, some of these blocks that we tried to scan above might have been missing because the node discarded them. Therefore, we decide to fetch these blocks from the node's peers and scan them.I've also added some unit tests to make sure each part of the workflow above works as expected.It's important to get this right, so it would be really helpful if people could spin up this patch locally on one of their test nodes and see how things go.This depends on Roasbeef/btcwallet#14, so you'll either need to pull that locally as well or make the changes yourself within thevendor
directory (very minor).I realize there's a lot of code duplication, it might be worth looking into how to refactor some of these things. That's probably out of scope for this PR though.