-
Notifications
You must be signed in to change notification settings - Fork 186
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
Add sideloading functionality to neutrino #285
base: master
Are you sure you want to change the base?
Conversation
24a87f6
to
916eb57
Compare
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 looks pretty nice! I think we are in the right track here 👍
I left some comments, many are about naming, styling and formatting but there are others about the feature logic that we need to address 🙏
Thanks @positiveblue , please I left comments on your review and pushed some changes in response to your feedback (the ones that I am clear on). |
214a0e6
to
26ffe03
Compare
Hello @positiveblue, have you had the chance to look at this again. If there are no objections to my comment. I will just |
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.
Second round done. I think it's taking shape 🥇 but there is still some things to address.
Currently unit test are not passing and the linter is also failing. You can check the github actions to get more details 🙏
Should we include the side loading for cfilters in this PR or you prefer to leave it for a next one?
Hello can anyone please run the workflow? |
Please can anyone help run the workflow? |
hmm, this works fine locally. I think I would just write a simple unit test instead of the integration test. |
@Chinwendu20 have you run it with the race condition detector locally? |
Oh no I did not @Roasbeef but I have made some changes now and ran it with the race detector, hopefully, this works on the CI as well. Can anyone please help run the workflow? |
Oh okay I will just mock the reader interface and write a unit test now. |
Please can anyone help run the workflow |
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 so far on this feature!
Completed an initial review, with the following jumping out:
- Style guide not closely adhered to (godoc string, 80 char columns, etc)
- The main set of interfaces can be simplified.
- The control flow can be simplified: if we have more headers than in the file, we can terminate. We should check the file before starting up any other sub-system. The file can just have the header be removed, then appended in one step to the end of
headers.bin
. - We can also use the side loader for the filter headers as well. I think this is where that generic param can actually be used.
- No need for custom re-org logic, just load the file if it's relevant, then let the normal p2p logic handle reorgs if needed.
chaindataloader/binary.go
Outdated
|
||
First 17 bytes holds the following information: | ||
|
||
- First 8 bytes holds the height of the first block header in the binary file (start 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.
If we want to compress things a bit more, then we can use a varint here, so: https://github.com/lightningnetwork/lnd/blob/3b7cda9e8d3a493fd548077a6cd6d5b8fa4b76bb/tlv/varint.go#L15
The comment should also be wrapped to 80 character columns as dictated by our style guide. I think it can be compressed to just:
Each file has a header consisting of:
firstHeight || lastHeight || chainType
.
Also we can just use an integer for the chain type, so assign values 1-4 to: mainnet, testnet, simnet, regtest, etc.
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.
Thanks, I would look into this.
chaindataloader/binary.go
Outdated
|
||
if _, err := b.file.ReadAt(rawBlkHdr, b.offset+(headerfs.BlockHeaderSize*b.tracker)); err != nil { | ||
if err == io.EOF { | ||
return nil, ErrEndOfFile |
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 think it can just pass thru io.EOF
as normal?
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.
blockmanager.go
Outdated
return | ||
default: | ||
// Request header | ||
header, headerErr := b.sideLoadBlkHdrReader.NextHeader() |
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.
Should read batches of headers at a time. We can also verify batches of headers at a time.
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.
Just like it is done in handleheaders
? From what I understand even though we read in batch we would have to verify the headers one after the other.
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.
Yes we can read them in as a batch, verify one by one (validity of header N depends on the validity of header N-1), then write as a batch.
blockmanager.go
Outdated
|
||
// Verify checkpoint only if verification is enabled. | ||
if b.nextCheckpoint != nil && | ||
node.Height == b.nextCheckpoint.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.
We should feed all this through our normal header processing logic. Some refactoring might be 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.
You mean refactoring the handleheaders
function?
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.
For regular block headers, I think the sideloading should be in blockManager.Start()
after the blockHandler
goroutine has started. Then it can call handleHeadersMsg
with the set of block headers. The headersMsg
struct might need to be modified since there's no peer
-- maybe a sideloading
bool so we can error if things go wrong. This also gets rid of the code duplication
For filter headers, something similar can be done where the sideloading is in Start()
.
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 can't place it after the blockhandler goroutine because we want to sideload before connecting to the network to fetch block headers.
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.
IMO it's better to have this be distinct, as then we don't need to increase the set of responsibilities of the blockManager
.
Logic similar to the following can validate all teh headers at oce:
for height := uint32(1); height <= bestBlockHeight; height++ {
if currHeader.PrevBlock != prevHeader.BlockHash() {
return fmt.Errorf("block header at height %d does "+
"not refrence previous hash %v", height,
prevHeader.BlockHash().String())
}
parentHeaderCtx := newLightHeaderCtx(
int32(height-1), &prevHeader, headerStore, nil,
)
skipDifficulty := blockchain.BFFastAdd
err = blockchain.CheckBlockHeaderContext(
&currHeader, parentHeaderCtx, skipDifficulty, chainCtx,
true,
)
if err != nil {
return fmt.Errorf("error checking block %d header "+
"context: %w", height, err)
}
err = blockchain.CheckBlockHeaderSanity(
&currHeader, chainCtx.params.PowLimit, timeSource,
skipDifficulty,
)
if err != nil {
return fmt.Errorf("error checking block %d header "+
"sanity: %w", height, err)
}
// TODO: Validate checkpoint and filter headers.
prevHeader = currHeader
if height > 0 && height%100_000 == 0 {
log.Debugf("Validated %d headers", height)
}
}
We'd then:
- Read a chunk of
N
headers (say 2k or so) from the side loader source. - Validate them all at once.
- Write them all to disk directly.
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.
Cool, I thought we did not want to duplicate the functionality of the handleheaders
function #285 (comment)
Hi @Chinwendu20, I'd love to implement this into a wallet I'm working on for Joltz Rewards (https://joltzrewards.com). Let me know if you'd like any support in addressing this PR review. Happy to dedicate time towards it. |
Thanks for the review left some comments and would implement changes that I am clear on. |
@Roasbeef: review reminder |
ee234b0
to
1f87365
Compare
@ellemouton, you can review now, thank you so much. I have not yet included a commit to remove code that is now redundant in blockmanger. As well as updated the blockmanager to use the validator and writer that was created in this PR. I just want to know if it is in line with Laolu's comments concerning encapsulation and using interfaces in the chaindataloader package and if the design is okay in general. |
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.
@Chinwendu20 - I think the overall idea of the various interfaces & abstractions along with the decoupling from the block manager look good 👍 would be good to get an opinion from @Roasbeef again though.
I think a good next step would be to spend some time getting the PR in a working & readable state. As is, it is hard to see what fits where just by going through the commits. So it is hard to give detailed feedback. As a reviewer, we want to be able to play around with the code. And then it is also easier to give feedback about whether or not an interface looks good.
Adding working tests would be awesome too cause that is often used by reviewers to et familiar with the changes & the reasons for various decisions.
Kudos for tackling this! This is quite a big project 🥇
4ce5ab9
to
d5bed2f
Compare
@@ -39,7 +42,7 @@ var ( | |||
// btclog.LevelOff turns on log messages from the tests themselves as | |||
// well. Keep in mind some log messages may not appear in order due to | |||
// use of multiple query goroutines in the tests. | |||
logLevel = btclog.LevelOff | |||
logLevel = btclog.LevelInfo | |||
syncTimeout = 30 * time.Second |
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 will revert this
SkipVerify: cfg.BlkHdrSideload.SkipVerify, | ||
Chkpt: blockHdrChkptMgr, | ||
SideloadRange: SideloadRange, | ||
} |
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.
Please note that I have not modified the blockmanager yet, if my approach is okay, the PR that I will submit would not have these.
return curHeight + s.SideloadRange, 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.
I am thinking there should be nothing like sideload range and we just fetch from checkpoint to checkpoint just as the blockmanager currently does whether we are verifying or not.
Also this just returns the last height and header that should be fetched in the next fetch. I would fix the comment and name of function.
41da324
to
5269c1b
Compare
55218d5
to
dd04f61
Compare
|
||
require.NoError( | ||
t, tlv.WriteVarInt(encodedOsFile, c.StartHeight, &[8]byte{}), | ||
) |
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 would jut use one scratch buffer for these
sideload/test_utils.go
Outdated
// headerBufPool is a pool of bytes.Buffer that will be re-used by the various | ||
// headerStore implementations to batch their header writes to disk. By | ||
// utilizing this variable we can minimize the total number of allocations when | ||
// writing headers to disk. |
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.
copy pasta - ignore comment for now.
This commit introduces the `sideload` package, designed to facilitate the sideloading of Bitcoin blockchain headers from external sources. Key components and changes: - **Interfaces and Core Types**: Introduction of several interfaces and types such as `SourceType`, `dataType`, `dataSize`, `HeaderValidator`, `HeaderWriter`, `Checkpoints`, and `LoaderSource` to abstract the concepts of blockchain header validation, storage, and source management. - **Loader Implementation**: The core of the sideload functionality is encapsulated in the `SideLoader` struct, which includes logic for header fetching, validation, and writing. - **Binary Loader for Headers**: An implementation of the LoaderSource interface for binary encoded headers is included in this commit Signed-off-by: Ononiwu Maureen <[email protected]>
This commit introduces a new `Checkpoints` structure for managing block header checkpoints. Motivation: Decoupling the logic for finding next and previous header checkpoints from the `blockmanager`, facilitating sharing this functionality between the `sideload` package and `blockmanager`, promoting code reuse and consistency across the components. Signed-off-by: Ononiwu Maureen <[email protected]>
This commit introduces a new structure to decouple the process of validating `wire.BlockHeaders` from the blockmanager. Signed-off-by: Ononiwu Maureen <[email protected]>
This commit introduces a new structure to decouple the process of writing `wire.BlockHeaders` to the block header store from the blockmanager. Signed-off-by: Ononiwu Maureen <[email protected]>
This commit adds the sideoading functionality to neutrino's chainservice. Signed-off-by: Ononiwu Maureen <[email protected]>
Signed-off-by: Ononiwu Maureen <[email protected]>
Related issue #70
This issue adds the sideloading functionality in neutrino. We want to be able to preload the block header and filter header store with headers before neutrino starts up. This optimizes speed.
Change summary: