Kernel: Remove BlockTreeEntry from the API#43
Conversation
| typedef void (*btck_ValidationInterfaceBlockDisconnected)(void* user_data, btck_Block* block, const btck_BlockTreeEntry* entry); | ||
| typedef void (*btck_ValidationInterfacePoWValidBlock)(void* user_data, btck_Block* block, const btck_Chain* chain); | ||
| typedef void (*btck_ValidationInterfaceBlockConnected)(void* user_data, btck_Block* block, const btck_Chain* chain); | ||
| typedef void (*btck_ValidationInterfaceBlockDisconnected)(void* user_data, btck_Block* block, const btck_Chain* chain); |
There was a problem hiding this comment.
Since the block can be retrieved from the chain tip, should block even be passed in the callback?
Also, I wonder why the block is mutable here. Is this callback supposed to be able to modify the block?
| }; | ||
|
|
||
| class BlockTreeEntry : public View<btck_BlockTreeEntry> | ||
| class ChainView : public View<btck_Chain> |
There was a problem hiding this comment.
ChainView should inherit from std::ranges::view_interface<ChainView>.
|
|
||
| BlockTreeEntry GetAncestor(int32_t height) const | ||
| //! The longest common prefix of this chain and another, beginning at genesis. | ||
| std::optional<ChainView> Mismatch(const ChainView& other) const |
There was a problem hiding this comment.
mismatch should return std::ranges::mismatch_result.
There was a problem hiding this comment.
Yes ok but performance, so what do you think about the compromise below. std::ranges::mismatch is O(n) this solution is O(log n) the 'compare' is walking from genesis to tip to find a mismatch (also side rant why from genesis ? such a deep reorg is unfeasible)
std::optional<ChainView> FindFork(const ChainView& other) const
{
auto fork{btck_chain_find_fork(get(), other.get())};
if (!fork) return std::nullopt;
return ChainView{fork};
}
std::ranges::mismatch_result<std::optional<ChainView>, std::optional<ChainView>>
Mismatch(const ChainView& other) const
{
auto fork{FindFork(other)};
int32_t fork_height{fork ? fork->Height() : -1};
std::optional<ChainView> in1{fork_height < Height() ? std::optional{GetAncestor(fork_height + 1)} : std::nullopt};
std::optional<ChainView> in2{fork_height < other.Height() ? std::optional{other.GetAncestor(fork_height + 1)} : std::nullopt};
return {in1, in2};
}(in this case btck_chain_mismatch() is renamed to btck_chain_find_fork() which also matches the result better)
There was a problem hiding this comment.
Yes ok but performance
Knuth would like a word ;)
Take what I say with a grain of salt as I haven't dug into the code yet, but I am suspicious of performance if it sacrifices legibility and makes this hard to test. Even more suspicious if its not in a critical path (e.g., mining, IBD, block validation, etc).
There was a problem hiding this comment.
What the mismatch member function does internally is an implementation detail and can be changed at any time. The only promise it should make is that it does produces the exact same outcome as std::ranges::mismatch but with a potentially better runtime complexity than
There should be a test that covers this:
TEST_CASE("mismatch member matches standard free function")
{
auto chain1 = ...;
auto chain2 = ...;
auto res1 = chain1.mismatch(chain2);
auto res2 = std::ranges::mismatch(chain1, chain2);
CHECK(std::same_as<decltype(res1), decltype(res2)>);
CHECK(res1.in1 == res2.in1);
CHECK(res1.in2 == res2.in2);
}b66cc0e to
08ea314
Compare
|
Concept ACK Nice! I'll read the blog post before reviewing the implementation, but love to see it. This also seems like an excellent candidate to upstream. |
08ea314 to
4a51f83
Compare
|
Did a re-read of the blogpost and the comments here, that made me change the approach (a bit) to match the idea of the blogpost better. Additionally: |
Back btck_Chain by a CBlockIndex instead of a CChain, so a chain becomes a snapshot identified by its tip block. A chain and the block tree entry at its tip then describe the same thing, making btck_BlockTreeEntry redundant; remove it and fold its accessors into btck_Chain. A null btck_Chain* now denotes the empty chain: all chain accessors are null-safe and chain-returning functions return the empty chain instead of an error, so ARG_NONNULL is dropped from chain parameters. In the C++ wrapper, rename ChainView to Chain and model it as a std::ranges range of its block headers (value_type = BlockHeader): operator[] indexes by height, size() - 1 is the height, and emptiness is observable via operator bool(). Mismatch mirrors std::ranges::mismatch but uses find_fork (O(log n)).
4a51f83 to
70e098a
Compare
This PR replaces the
btck_BlockTreeEntrytype in the kernel C API with a single chain-oriented abstraction,btck_Chain, matching the end state described in https://purplekarrot.net/blog/drop-block-tree-entry-from-bitcoin-kernel.html.Since a block uniquely determines its path to genesis, a chain is fully determined by its tip and shares the same underlying CBlockIndex. Consolidating on
btck_Chainalso better hides the kernel's internals, exposing an opaque chain handle rather than leaking the block-index structure into the API (leaving the kernel free to change that without breaking the C API).New chain functions:
btck_chainstate_manager_get_active_chain / _get_best_chain / _find -> return chains
btck_chain_get_height
btck_chain_get_block_header / _get_block_hash / _get_ancestor
btck_chain_get_parent
btck_chain_starts_with / _find_fork / _equals
This design follows @purpleKarrot's writeup and parts of the approach are based on his blogpost ofc. happy to add a Co-authored-by: if he's comfortable with it (quality wise) .