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

blockchain, btcjson: Implement getchaintips rpc call #1918

Merged

Conversation

kcalvinalvin
Copy link
Collaborator

@kcalvinalvin kcalvinalvin commented Nov 7, 2022

getchaintips rpc call is implemented and follows the algorithm that Bitcoin Core has in https://github.com/bitcoin/bitcoin/blob/50422b770a40f5fa964201d1e99fd6b5dc1653ca/src/rpc/blockchain.cpp#L1374.

Test coverage checks that the height, block hash, branchlen, and status are correct. I also tested the rpc on regtest with blocks:

78b945a390c561cf8b9ccf0598be15d7d85c67022bf71083c0b0bd8042fc30d7
1b97df2be6c634e40a5f410f4758ba707c4452cda6f3707b4935c9e7760df965

which both build on top of the genesis block.

0000002006226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f467c23519a04a309fb31f27572a5e8e8eadb9f1881a7c12b30a06438bdde486c5cca6463ffff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff025100ffffffff0200f2052a01000000160014e96f87294541ef0574ba422198c73e702e22d7a60000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000

raw blocks:

0000002006226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f0cf276b0154586bd0755c963c2d2f4471504a8450575e30a7273d56b7e31f7cc7acf6463ffff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff025100ffffffff0200f2052a010000001600149c349625c03446ec5b22f41d78d6089b82b909950000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000

block: 716c71f0c4990732b51dc611825fdd11fc8dc471ffc3e5c1de86b5d6e4663ff2 builds on top of block 78b945a390c561cf8b9ccf0598be15d7d85c67022bf71083c0b0bd8042fc30d7 and it was used to test reorging and making sure getchaintips behavior matches that of bitcoin core.

raw block:

00000020d730fc4280bdb0c08310f72b02675cd8d715be9805cf9c8bcf61c590a345b9782b5d65a911e677d43c235c04fd504eb1c2c348b8853c0fcf10670c2c330949779fcf6463ffff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff025200ffffffff0200f2052a01000000160014d0b47c9d09352c365bef96a0ed1fc67ac858e3140000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000

Resolves #1912

@kcalvinalvin kcalvinalvin marked this pull request as draft November 7, 2022 06:31
@kcalvinalvin kcalvinalvin force-pushed the 2022-11-06-implement-getchaintips branch from a75d949 to 861aebb Compare November 7, 2022 06:33
@coveralls
Copy link

coveralls commented Nov 7, 2022

Pull Request Test Coverage Report for Build 3425832785

Warning: This coverage report may be inaccurate.

This pull request's base commit is no longer the HEAD commit of its target branch. This means it includes changes from outside the original pull request, including, potentially, unrelated coverage changes.

Details

  • 62 of 105 (59.05%) changed or added relevant lines in 4 files are covered.
  • 218 unchanged lines in 4 files lost coverage.
  • Overall coverage decreased (-17.9%) to 55.198%

Changes Missing Coverage Covered Lines Changed/Added Lines %
blockchain/chain.go 57 64 89.06%
rpcclient/chain.go 0 18 0.0%
rpcserver.go 0 18 0.0%
Files with Coverage Reduction New Missed Lines %
blockchain/blockindex.go 2 95.35%
peer/peer.go 4 73.2%
blockchain/chain.go 57 73.13%
rpcclient/chain.go 155 3.04%
Totals Coverage Status
Change from base Build 3423315641: -17.9%
Covered Lines: 26617
Relevant Lines: 48221

💛 - Coveralls

@kcalvinalvin kcalvinalvin marked this pull request as ready for review November 7, 2022 06:38
@kcalvinalvin kcalvinalvin force-pushed the 2022-11-06-implement-getchaintips branch from 861aebb to eee2537 Compare November 7, 2022 06:40
@0xB10C
Copy link

0xB10C commented Nov 7, 2022

I'm not familiar with the btcd process for this: How/when is json_rpc_api.md updated? Automatically when doing a release or manually in PRs? In case of the latter, adding RPC docs here would be good.

Comment on lines 128 to 131
// GetChainTipsResult models the data from the getchaintips command.
type GetChainTipsResult struct {
ChainTips []ChainTip `json:"chaintips"`
}
Copy link

Choose a reason for hiding this comment

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

I'm not sure how closely you want to follow the Bitcoin Core RPC response. From the docs I got the impressions that some RPC calls are/were compatible with Bitcoin Core.

Bitcoin Core's getchaintips RPC returns a JSON list of chain tips as top level element:

[
  {
     "height": 115861,
     "hash":" 000000407476239a4870e510ce1dea262c9afa672eae3ab50a019812f83e0059",
     "branchlen": 0,
     "status": "active"
  }
]

This currently returns a JSON map with a single key called chaintips and the list of chain tips as value.

"chaintips": [
  {
     "height": 115861,
     "hash":" 000000407476239a4870e510ce1dea262c9afa672eae3ab50a019812f83e0059",
     "branchlen": 0,
     "status": "active"
  }
]

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Changed the output to match that of Bitcoin Core

@kcalvinalvin
Copy link
Collaborator Author

I'm not familiar with the btcd process for this: How/when is json_rpc_api.md updated? Automatically when doing a release or manually in PRs? In case of the latter, adding RPC docs here would be good.

By the looks of it from this commit a6c79c7, seems like you need to manually update the log when you implement the rpc call. I'll update the file.

@kcalvinalvin kcalvinalvin force-pushed the 2022-11-06-implement-getchaintips branch 2 times, most recently from b42df7f to 566b618 Compare November 9, 2022 06:36
@kcalvinalvin
Copy link
Collaborator Author

kcalvinalvin commented Nov 9, 2022

Updated json_rpc_api.md and change the RPC response to match that of Bitcoin Core

@kcalvinalvin kcalvinalvin changed the title blockchain, btcjson: Implement getchaintips rpc all blockchain, btcjson: Implement getchaintips rpc call Nov 9, 2022
Copy link
Member

@Roasbeef Roasbeef 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 picking this up!

One thing I'd like to get back in the habit of is adding more rpctests. This would let us exercise the new RPC interface a bit more by doing things like mining invalid blocks, creating forks, etc -- then asserting that the final output is what we'd expect.

blockchain/blockindex.go Outdated Show resolved Hide resolved
blockchain/blockindex.go Outdated Show resolved Hide resolved
blockchain/chain.go Show resolved Hide resolved
blockchain/chain.go Outdated Show resolved Hide resolved
blockchain/chain.go Show resolved Hide resolved
Height int32
// BlockHash hash of the tip.
BlockHash chainhash.Hash
// Amount of blocks connecting this tip with the best chain. Returns
Copy link
Member

Choose a reason for hiding this comment

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

A bit confused re the wording here: this is meant to the the height gap between this non-main chain tip and the main chain itself?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It's meant as the amount of blocks the branch has from the oldest common ancestor with the active chain. Active chain always returns 0.

The below branch of "unknown" would have a branchlen of 2 since it forks off at block 10 and has 2 blocks built on top of block 10.

//      genesis -> 1 -> 2 -> 3 ... -> 10 -> 11  -> 12  -> 13 (active)
//                                      \-> 11a -> 12a (unknown)

Copy link
Member

Choose a reason for hiding this comment

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

Makes a lot of sense, I think we can add some more of this context to the comment itself (can even add that diagram above).

blockchain/chain.go Show resolved Hide resolved
blockchain/chain.go Outdated Show resolved Hide resolved
blockchain/chain.go Outdated Show resolved Hide resolved
blockchain/chain_test.go Outdated Show resolved Hide resolved
@kcalvinalvin kcalvinalvin force-pushed the 2022-11-06-implement-getchaintips branch from 566b618 to d3086cd Compare November 11, 2022 05:42
@kcalvinalvin
Copy link
Collaborator Author

kcalvinalvin commented Nov 11, 2022

Addressed all of the modifications requested. I'm not too familiar with how rpctests work. I'll look into it and add it as a new commit.

Edit: From a glance the existing rpc test look like the functional tests in core. Maybe importing the functional tests from core is something that could be desirable in the future?

@kcalvinalvin
Copy link
Collaborator Author

Added a new commit that adds a test for the getchaintips call in the integration tests.

@Roasbeef
Copy link
Member

Roasbeef commented Jan 6, 2023

Edit: From a glance the existing rpc test look like the functional tests in core. Maybe importing the functional tests from core is something that could be desirable in the future?

Yeah they're similar-ish: main idea is to be able to drive nodes over RPC to trigger certain p2p/mempool/chain behavior. Re just porting over the Bitcoin Core functional tests: we ended up going this route so we'd only have a single language in the repo (just Go). If we started to port their tests directly, then we'd also need to ensure a Python CI env and the proper docs to get things up and running. Over time though, our RPC interface has started to drift away from theirs, so there would be certain tests that just wouldn't be compatible at all. Nevertheless, it'd basically be test coverage we'd get for free, so it's def worth looking into.

@Roasbeef Roasbeef requested a review from Crypt-iQ January 6, 2023 01:36
Copy link
Member

@Roasbeef Roasbeef left a comment

Choose a reason for hiding this comment

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

PR is shaping up pretty well, really dig the extra commit to add an integration test!

// of the orphans.
orphans := make(map[chainhash.Hash]*blockNode)
orphanParent := make(map[chainhash.Hash]*blockNode)
for hash, node := range bi.index {
Copy link
Member

Choose a reason for hiding this comment

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

Seeing index interactions like this makes we wish we had that skip list structure integrated 🤓

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Ah yeah I had a PR open for that a long time ago. Should clean that up too.

// BlockHash hash of the tip.
BlockHash chainhash.Hash

// BranchLen is the amount of blocks connecting this tip with the best chain.
Copy link
Member

Choose a reason for hiding this comment

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

Potential comment reframing:

BranchLen is length of the fork point of this chain from the main chain

?


chainTips := make([]ChainTip, 0, len(tips))

// Go through all the tips and grab the height, hash, branch length, and the block
Copy link
Member

Choose a reason for hiding this comment

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

Comments below not wrapped.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

All of the comments below are under 80 column though (with 8 space tabs). Could you point out which ones should be wrapped?

// status.
for _, tip := range tips {
var status TipStatus
if b.bestChain.Contains(tip) {
Copy link
Member

Choose a reason for hiding this comment

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

Potentially more readable as a switch statement (then the comments can go right above where the case lies).

}

for _, test := range tests {
chain, expectedChainTips := test.chainTipGen()
Copy link
Member

Choose a reason for hiding this comment

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

Same comment re line folding below.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Is there a desirable column width limit? There's many parts of btcd code that's over 100.

(My personal taste is that anything under 110 is acceptable)

return fmt.Errorf("Couldn't find expected chaintip with hash %s", expectedChainTip.Hash)
}

err := compareChainTips(gotChainTip, *expectedChainTip)
Copy link
Member

Choose a reason for hiding this comment

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

Alternatively can just use something like require.Equal, it'll print out where the diff is, vs needing to scan thru to find the field, and also if more fields are added, it won't need to be modified.

func TestGetChainTips(t *testing.T) {
// block1Hex is a block that builds on top of the regtest genesis block.
// Has blockhash of "36c056247e8c0589f6307995e4e13acf2b2b79cad9ecd5a4eeab2131ed0ecde5".
block1Hex := "0000002006226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf18891" +
Copy link
Member

Choose a reason for hiding this comment

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

Move to the top of the file as constants?

Copy link
Member

Choose a reason for hiding this comment

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

Alternatively, we could also just generate these blocks. This method would let us mine empty blocks at will. The test would then use a series of miners (connect, mine block, disconnect, let it mine its own blocks, etc) to dynamically create the chain.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Alternatively, we could also just generate these blocks.

We could and that'd be preferable. But at the moment there's no support for invalidateblock so we can't test for reorgs.

A timeline like below would work:
Get this PR in -> get invalidateblock in -> change tests to generate blocks

// Our chain view looks like so:
// (genesis block) -> 1 -> 2 -> 3 -> 4
blockStrings := []string{block1Hex, block2Hex, block3Hex, block4Hex}
for _, blockString := range blockStrings {
Copy link
Member

Choose a reason for hiding this comment

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

So with the comment above, these would just be mined w/ empty params (will auto use the prior to build off of).

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yup. But as mentioned in here, we can't do that quite yet.


// Submit a single block that builds on top of 3a.
//
// Our chain view looks like so:
Copy link
Member

Choose a reason for hiding this comment

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

Solid test: 👍

BranchLen: 1,
Status: "valid-fork",
},
}
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 there's a missing call to compareMultipleChainTips here (lack of assertion for final case).

@Roasbeef
Copy link
Member

Interested in finishing this up for it to land?

InactiveTips() returns all the tips of the branches of the blockchain
tree that are not in the best chain. This function is useful for
supporting the getchaintips rpc call.
ChainTips method allows for callers to get all the chain tips the node
is aware of. This is useful for supporting the getchaintips rpc call.
getchaintips call is implemented and the behavior mimics that of Bitcoin
Core. Resolves btcsuite#1912.
rpcclient now support calling the getchaintips rpc call.

getchaintips_test.go adds test for the getchaintips rpc call. The test
includes hard-coded blocks which the test will feed the node via rpc and
it'll check that the returned chain tips from the getchaintips call
are chaintips that we expect to be returned.
@kcalvinalvin kcalvinalvin force-pushed the 2022-11-06-implement-getchaintips branch from 5924bb4 to fc99e96 Compare July 16, 2023 07:04
@kcalvinalvin
Copy link
Collaborator Author

Addressed most things in the review and asked for clarification on the rest

@saubyk
Copy link

saubyk commented Jul 31, 2023

Hi @Roasbeef this pr is also ready for your next round of review

Copy link
Member

@Roasbeef Roasbeef left a comment

Choose a reason for hiding this comment

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

LGTM 🧩

@Roasbeef Roasbeef merged commit f7e9fba into btcsuite:master Nov 15, 2023
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.

Implement getchaintips RPC
5 participants