Skip to content

Conversation

@ryanofsky
Copy link
Contributor

@ryanofsky ryanofsky commented Feb 8, 2024

Changes making it possible to call interface::Chain over a socket.

Note: The changes in this PR allow a bitcoin-node process to be controlled over a socket. The socket can either be an UNIX socket created by specifying the -ipcbind option, or it can be a socketpair created by a controlling process and passed to bitcoin-node via the -ipcfd=<n> with a file descriptor number. This PR is a dependency of #10102 which allows the wallet code to run in a separate process and use the Chain interface.


This PR is part of the process separation project.

@DrahtBot
Copy link
Contributor

DrahtBot commented Feb 8, 2024

The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

Code Coverage & Benchmarks

For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/29409.

Reviews

See the guideline for information on the review process.

Type Reviewers
Concept ACK josibake, darosior
Stale ACK cbergqvist, sedited

If your review is incorrectly listed, please copy-paste <!--meta-tag:bot-skip--> into the comment that the bot should ignore.

Conflicts

Reviewers, this pull request conflicts with the following ones:

  • #32297 (bitcoin-cli: Add -ipcconnect option by ryanofsky)

If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

LLM Linter (✨ experimental)

Possible typos and grammar issues:

  • ensure that serializing and deserializing objects -> ensure that serializing and deserializing objects use the same parameters [the original line is an incomplete sentence and thus unclear; adding "use the same parameters" completes the thought and restores comprehensibility]

2026-01-06

@ryanofsky ryanofsky marked this pull request as ready for review February 8, 2024 14:04
@DrahtBot
Copy link
Contributor

DrahtBot commented Feb 8, 2024

🚧 At least one of the CI tasks failed. Make sure to run all tests locally, according to the
documentation.

Possibly this is due to a silent merge conflict (the changes in this pull request being
incompatible with the current code in the target branch). If so, make sure to rebase on the latest
commit of the target branch.

Leave a comment here, if you need help tracking down a confusing failure.

Debug: https://github.com/bitcoin/bitcoin/runs/21365328047

Copy link
Contributor

@cbergqvist cbergqvist left a comment

Choose a reason for hiding this comment

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

I'm eager for multiprocess to get in, but unable to do in-depth reviews in tolerable time due to lack of domain-knowledge. Did a surface level review of 2fd1042. Hope you don't mind.

Copy link
Contributor Author

@ryanofsky ryanofsky 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 the review!

Updated 2fd1042 -> 41831ab (pr/ipc-chain.2 -> pr/ipc-chain.3, compare) with suggested changes.

Copy link
Contributor

@cbergqvist cbergqvist 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 incorporating some of my suggestions. Surface-level ACK of 41831ab. :)

Copy link

@ariard ariard left a comment

Choose a reason for hiding this comment

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

being able to work with any socket (TCP or UNIX) makes a lot of sense.
you could have bitcoin-node on some secure computing host.
and then one or more bitcoin-walet on over-the-network hosts.

template<typename S>
auto Wrap(S& s)
{
return ParamsStream{s, TX_WITH_WITNESS, CAddress::V2_NETWORK};
Copy link

Choose a reason for hiding this comment

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

i think Wrap could be parameterized with any CAddress version.
e.g useful if someone wanna test the native crypto noise framework between bitcoin processes.
or in the future experiment with better crypto-primitives for bip324.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

re: #29409 (comment)

i think Wrap could be parameterized with any CAddress version. e.g useful if someone wanna test the native crypto noise framework between bitcoin processes. or in the future experiment with better crypto-primitives for bip324.

For now the Wrap function is basically a workaround for not having a serialization option that says "serialize all the data in this object to a stream, I don't care about the specific serialization format." So the formats here just chosen to preserve all data, and don't really matter otherwise.

But I could imagine use-cases like you are suggesting where we may want to customize which serialization format is used depending on the IPC connection. I think it would not be that hard to do by adding parameters to Wrap, which is not called many places, or by writing different CustomReadField/BuildField overloads which do not call Wrap. For now though the easiest thing is for the wrapped stream to just hardcode parameters that serialize all the data.

@DrahtBot DrahtBot marked this pull request as draft April 17, 2024 17:34
@ryanofsky ryanofsky marked this pull request as ready for review June 11, 2024 01:55
@sedited
Copy link
Contributor

sedited commented Jul 5, 2024

I'm not sure what a good approach for reviewing this might be. How can I validate the changes without tests? Should I be running this as part of a parent pull request? The first two commits seem fairly mechanical, but the last one less so. The comments in the code are helpful, but some pointers in the PR description for what to look out for, or a broad description of the approaches taken would be helpful too. E.g. why is so much custom functionality introduced for findBlock?

@ryanofsky
Copy link
Contributor Author

re: #29409 (comment)

Thanks for the questions.

I'm not sure what a good approach for reviewing this might be. How can I validate the changes without tests? Should I be running this as part of a parent pull request?

You could test this as part of #10102, and I should probably add some unit test code to ipc_test.cpp to make sure new runtime code in capnp/chain.cpp and capnp/common-types.h functions has test coverage.

Most of the code in the PR is evaluated at compile time though, so for example, if there is a mismatch in any of the interface declarations there will be build errors.

The first two commits seem fairly mechanical, but the last one less so. The comments in the code are helpful, but some pointers in the PR description for what to look out for, or a broad description of the approaches taken would be helpful too.

This is helpful feedback, and I will try to add more comments. I know it needs more comments, but it's hard for me to know where to add them without being asked because I'm too familiar with everything here. This PR is a case where "Don't spend more than a few seconds trying to understand it" review really advice applies, so if something doesn't make sense, you should just ask so I can clarify. If something doesn't make sense to you, it probably doesn't make sense to other people either.

E.g. why is so much custom functionality introduced for findBlock?

Will add a comment, but Chain::findBlock and the handful of other Chain methods taking FoundBlock input/output arguments need custom code because these are converted to FoundBlockParam and FoundBlockResult Cap'n Proto structs to make IPC calls, and these structs are recursive. Libmultiprocess has decent support for converting back and forth between C++ classes and capnp structs generally, but it would be hard for it support recursive structs. It also might have a hard time in this case because there is a not 1:1 mapping between fields in the c++ class and capnp structs. So the conversion for these types is completely custom.

Similar custom code also exists for the C++ BlockInfo struct which is complicated to translate to the Capnp BlockInfo struct, because it has internal pointers and references, so it has custom code because it can't really be converted automatically.

Copy link
Contributor Author

@ryanofsky ryanofsky left a comment

Choose a reason for hiding this comment

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

Updated ab6b795 -> eec0d31 (pr/ipc-chain.5 -> pr/ipc-chain.6, compare), rebasing and dropping no longer needed code after libmultiprocess update, adding comments and tests, and splitting up commits. There is still more to do to splitting up commits and adding tests, however.

This PR now shares a little bit of code with #30510, so will get smaller if #30510 is merged. #30510 is also simpler and should be easier to review, so will mark this as a draft, although feedback is still welcome on this PR especially if there are questions or things that need more explanation.


re: #29409 (review)

being able to work with any socket (TCP or UNIX) makes a lot of sense. you could have bitcoin-node on some secure computing host. and then one or more bitcoin-walet on over-the-network hosts

Yes this PR does not actually expose a socket but #30509 does, and #19460 uses it for the wallet (and #19461 uses it for the gui, and Sjors#48 and #30437 use it for the mining interface

template<typename S>
auto Wrap(S& s)
{
return ParamsStream{s, TX_WITH_WITNESS, CAddress::V2_NETWORK};
Copy link
Contributor Author

Choose a reason for hiding this comment

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

re: #29409 (comment)

i think Wrap could be parameterized with any CAddress version. e.g useful if someone wanna test the native crypto noise framework between bitcoin processes. or in the future experiment with better crypto-primitives for bip324.

For now the Wrap function is basically a workaround for not having a serialization option that says "serialize all the data in this object to a stream, I don't care about the specific serialization format." So the formats here just chosen to preserve all data, and don't really matter otherwise.

But I could imagine use-cases like you are suggesting where we may want to customize which serialization format is used depending on the IPC connection. I think it would not be that hard to do by adding parameters to Wrap, which is not called many places, or by writing different CustomReadField/BuildField overloads which do not call Wrap. For now though the easiest thing is for the wrapped stream to just hardcode parameters that serialize all the data.

@ryanofsky ryanofsky marked this pull request as draft July 26, 2024 16:35
@sedited
Copy link
Contributor

sedited commented Sep 29, 2025

Err, right. Updated the description with your suggested trace filtering.

@ryanofsky
Copy link
Contributor Author

Err, right. Updated the description with your suggested trace filtering.

Thanks created bitcoin-core/libmultiprocess#219 to track this (feel free to create new issues directly in the future). Thanks for updating the rust client, too!

@ryanofsky
Copy link
Contributor Author

ryanofsky commented Sep 30, 2025

Copy link
Contributor

@l0rinc l0rinc left a comment

Choose a reason for hiding this comment

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

Rebased locally and did a first pass through the changes.
It seems to me the capnp wrapper needs some updates (missing field, 32/64 bit params, param rename, bool vs uint64, enums as unsigned, optional mapping, unused declarations, comments, etc).

Unrelated nit: while checking the code I noticed that RemoveCvRef and std::remove_cv_t<std::remove_reference_t can likely be changed to std::remove_cvref_t in a few places (should be done in a different PR, if valid)

# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.

@0x94f21a4864bd2c65;
Copy link
Contributor

Choose a reason for hiding this comment

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

Could we add a simple explanation comment for these salts?

Copy link
Contributor Author

@ryanofsky ryanofsky Dec 12, 2025

Choose a reason for hiding this comment

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

re: #29409 (comment)

Could we add a simple explanation comment for these salts?

Not sure about this. Is there a comment you would suggest? I feel like ids are built into the syntax of Cap'n Proto and ubiquitous. If you aren't familiar with Cap'n Proto, I think a comment here would probably just distract you from more semantically relevant parts of the file and leave you confused. Commenting on IDs in capnproto seems like commenting on #ifndef include guards at the top of c++ header files. It would be hard to know where to begin or what information would be helpful.

Comment on lines +19 to +20
destroy @0 (context :Proxy.Context) -> ();
getHeight @1 (context :Proxy.Context) -> (result :Int32, hasResult :Bool);
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder how schema evolution is handled by Cap'n Proto - what happens if a field is deprecated and removed or new values are inserted, do I have to check all existing values to make sure I can find a new valid id?

https://capnproto.org/language.html#evolving-your-protocol claims:

  • New fields, enumerants, and methods may be added to structs, enums, and interfaces, respectively, as long as each new member’s number is larger than all previous members. Similarly, new fields may be added to existing groups and unions.
  • New parameters may be added to a method. The new parameters must be added to the end of the parameter list and must have default values.
  • Members can be re-arranged in the source code, so long as their numbers stay the same.

So I guess if you change any value and deprecate the old one you will have to add the new version with max + 1 - wanted to suggest that we could add 10 opportunities between values like:

    destroy @10 ...
    getHeight @20...

so that when we change destroy to return a bool or add a new method, we can do it as:

    construct @0 ...
    destroy @10 ...  # deprecated
    destruct @11 ...
    getHeight @20...

but it appears that's not possible.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

re: #29409 (comment)

I wonder how schema evolution is handled by Cap'n Proto - what happens if a field is deprecated and removed or new values are inserted, do I have to check all existing values to make sure I can find a new valid id?

Yes you do. You need to pick the next unused ID. I think it actually works pretty well in practice, especially if the id's are mostly in order, but it's also possible to include # Next unused id: 42 comments if they are really out of order and it's hard to find the next one.

we could add 10 opportunities between values [...] but it appears that's not possible

Yes like you point out, gaps are not allowed. Technically I think the compiler could allow gaps for interfaces, but for structs it couldn't work because the order fields are added to the struct defines its binary layout. For interfaces the restriction also seems reasonable to make interfaces consistent with structs, and make it possible to see what order methods were added over time.

Since it's also possible to move and rename deprecated fields, this restriction doesn't get in the way of making capnp files readble. Renaming also lets you break source compatibility and require source code to be updated, while maintaining binary compatibility so old binaries and code using previous definitions continue to work. In general, I think the system is very flexible and functions pretty nicely.

Comment on lines 1 to 3
# Copyright (c) 2024 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
Copy link
Contributor

Choose a reason for hiding this comment

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

super-nit: I assume the copyright headers are similar to other code in the repo:

Suggested change
# Copyright (c) 2024 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
# Copyright (c) 2024-present The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or https://opensource.org/license/mit.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

re: #29409 (comment)

super-nit: I assume the copyright headers are similar to other code in the repo:

Nice catch, fixed this up

getTransactionAncestry @19 (context :Proxy.Context, txid :Data) -> (ancestors :UInt64, descendants :UInt64, ancestorsize :UInt64, ancestorfees :Int64);
calculateIndividualBumpFees @20 (context :Proxy.Context, outpoints :List(Data), targetFeerate :Data) -> (result: List(Common.PairInt64(Data)));
calculateCombinedBumpFee @21 (context :Proxy.Context, outpoints :List(Data), targetFeerate :Data) -> (result :Int64, hasResult :Bool);
getPackageLimits @22 (context :Proxy.Context) -> (ancestors :UInt64, descendants :UInt64);
Copy link
Contributor

Choose a reason for hiding this comment

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

May be deliberate, but the parameters in

virtual void getPackageLimits(unsigned int& limit_ancestor_count, unsigned int& limit_descendant_count) = 0;

are 32 bit

Copy link
Contributor Author

Choose a reason for hiding this comment

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

re: #29409 (comment)

May be deliberate, but the parameters in [...] are 32 bit

Good catch. Updated these. Using a bigger type was harmless but not intentional. (It is a compiler error if you use a smaller type.)

interface ChainNotifications $Proxy.wrap("interfaces::Chain::Notifications") {
destroy @0 (context :Proxy.Context) -> ();
transactionAddedToMempool @1 (context :Proxy.Context, tx :Data) -> ();
transactionRemovedFromMempool @2 (context :Proxy.Context, tx :Data, reason :Int32) -> ();
Copy link
Contributor

Choose a reason for hiding this comment

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

Original has enums for reason

virtual void transactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason) {}

should we make the param UInt32 like we did one line below for ChainstateRole

Suggested change
transactionRemovedFromMempool @2 (context :Proxy.Context, tx :Data, reason :Int32) -> ();
transactionRemovedFromMempool @2 (context :Proxy.Context, tx :Data, reason :UInt32) -> ();

Or vice versa, since Int32 seems more common (though UInt32 may make more sense).

But looking at #29409 (comment), you mentioned:

After bitcoin-core/libmultiprocess#120 it should also be a compile error if incompatible types are used

which was merged since - shouldn't it be a compile error now?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

re: #29409 (comment)

should we make the param UInt32 like we did one line below for ChainstateRole

Nice find noticing the inconsistency. Both types should be safe because casts between signed and unsigned types are well-defined. I tend to prefer signed types for enums so if the enums use -1, -2, -3 values they will be displayed nicely. But unsigned types could be better for some enums, particularly bitmasks. I updated chainstate role enums variables to be signed for consistency.

schedulerMockForward @7 (context :Proxy.Context, time :Int64) -> ();
}

struct FeeCalculation $Proxy.wrap("FeeCalculation") {
Copy link
Contributor

Choose a reason for hiding this comment

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

best_height seems to be missing:

unsigned int best_height{0};

introduced in
1a7fb5e#diff-722dfe5b8bcfb83b1d2f2c9b10800a873325eac8dfc343239f6c851220508c4eR98

Copy link
Contributor Author

Choose a reason for hiding this comment

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

re: #29409 (comment)

best_height seems to be missing:

Great catch! This is a real bug. Fixed and added comment to the C++ struct to prevent these getting out of sync in the future.

//! Depending on the action returned by the update function, this will either
//! update the setting in memory or write the updated settings to disk.
virtual bool updateRwSetting(const std::string& name, const SettingsUpdate& update_function) = 0;
virtual bool updateRwSetting(const std::string& name, SettingsUpdate update_function) = 0;
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we have to wait for SettingsValue to be exposed for the other consts to be removed as well?
Looks a bit weird that this isn't consted anymore, while other similar ones still are:

//! Update a setting in <datadir>/settings.json.
virtual void updateRwSetting(const std::string& name, const common::SettingsValue& value) = 0;
//! Force a setting value to be applied, overriding any other configuration
//! source, but not being persisted.
virtual void forceSetting(const std::string& name, const common::SettingsValue& value) = 0;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

re: #29409 (comment)

Do we have to wait for SettingsValue to be exposed for the other consts to be removed as well?

It looks like const references there could be removed, but did you mean to link to a different interface file? The node.h interface is used by the GUI, unlike the chain.h interface used by wallet. But it could be changed in a separate PR and I noted some other ideas for cleaning up the settings interfaces previously in #30697 (comment) and #30828 (review)

std::any context;
JSONRPCVersion m_json_version = JSONRPCVersion::V1_LEGACY;
// Note: If you add fields to this struct, you should also update the
// JSONRPCRequest struct in ipc/capnp/chain.capnp.
Copy link
Contributor

Choose a reason for hiding this comment

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

can't we auto-detect that via reflection maybe?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

re: #29409 (comment)

can't we auto-detect that via reflection maybe?

Yes there are different ways that could be detected automatically, not even requiring reflection necessarily. Like you could declare a duplicate struct and assert the sizes of the structs are the same. I think it would actually be possible for the code generator to do this automatically.

I do think the comment here makes sense even with additional enforcement, though since it should be helpful to anyone updating this code or reviewing changes to it.

return T({array.begin(), array.begin() + array.size()});
}

//! Convert base_blob to kj::ArrayPtr.
Copy link
Contributor

Choose a reason for hiding this comment

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

while ToArray seems to be used mostly for uint256, there are lots of DataStream calls to it which is not base_blob

Suggested change
//! Convert base_blob to kj::ArrayPtr.
//! Convert contiguous byte data (e.g. base_blob, DataStream) to kj::ArrayPtr.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

re: #29409 (comment)

while ToArray seems to be used mostly for uint256, there are lots of DataStream calls to it which is not base_blob

Thanks took your suggestion and also updated the ToBlob comment

{
// This method is never called because ChainClient::Start is overridden by
// WalletLoader::Start. The custom implementation is needed just because
// the CScheduler& argument this is supposed to pass is not serializable.
Copy link
Contributor

Choose a reason for hiding this comment

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

is this still accurate?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

re: #29409 (comment)

is this still accurate?

Yes the ChainClient::start method does still take a scheduler parameter (so the node and wallet can share a scheduler.

virtual void start(CScheduler& scheduler) = 0;

This could probably be changed, but the PR is trying to avoid changing the interface or changing behavior at all.

Copy link
Contributor Author

@ryanofsky ryanofsky 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 the close review, and sorry for the delay responding and updating this. You found a lot of annoying discrepencies and a real bug in the estimateSmartFee best_height return value, which should all be fixed now.

Rebased d9efd1e -> 89cf624 (pr/ipc-chain.21 -> pr/ipc-chain.22, compare) fixing conflict with #33774 and making suggested updates

Rebased 89cf624 -> b2cbdd3 (pr/ipc-chain.22 -> pr/ipc-chain.23, compare)

# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.

@0x94f21a4864bd2c65;
Copy link
Contributor Author

@ryanofsky ryanofsky Dec 12, 2025

Choose a reason for hiding this comment

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

re: #29409 (comment)

Could we add a simple explanation comment for these salts?

Not sure about this. Is there a comment you would suggest? I feel like ids are built into the syntax of Cap'n Proto and ubiquitous. If you aren't familiar with Cap'n Proto, I think a comment here would probably just distract you from more semantically relevant parts of the file and leave you confused. Commenting on IDs in capnproto seems like commenting on #ifndef include guards at the top of c++ header files. It would be hard to know where to begin or what information would be helpful.

Comment on lines +19 to +20
destroy @0 (context :Proxy.Context) -> ();
getHeight @1 (context :Proxy.Context) -> (result :Int32, hasResult :Bool);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

re: #29409 (comment)

I wonder how schema evolution is handled by Cap'n Proto - what happens if a field is deprecated and removed or new values are inserted, do I have to check all existing values to make sure I can find a new valid id?

Yes you do. You need to pick the next unused ID. I think it actually works pretty well in practice, especially if the id's are mostly in order, but it's also possible to include # Next unused id: 42 comments if they are really out of order and it's hard to find the next one.

we could add 10 opportunities between values [...] but it appears that's not possible

Yes like you point out, gaps are not allowed. Technically I think the compiler could allow gaps for interfaces, but for structs it couldn't work because the order fields are added to the struct defines its binary layout. For interfaces the restriction also seems reasonable to make interfaces consistent with structs, and make it possible to see what order methods were added over time.

Since it's also possible to move and rename deprecated fields, this restriction doesn't get in the way of making capnp files readble. Renaming also lets you break source compatibility and require source code to be updated, while maintaining binary compatibility so old binaries and code using previous definitions continue to work. In general, I think the system is very flexible and functions pretty nicely.

Comment on lines 1 to 3
# Copyright (c) 2024 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

re: #29409 (comment)

super-nit: I assume the copyright headers are similar to other code in the repo:

Nice catch, fixed this up

getTransactionAncestry @19 (context :Proxy.Context, txid :Data) -> (ancestors :UInt64, descendants :UInt64, ancestorsize :UInt64, ancestorfees :Int64);
calculateIndividualBumpFees @20 (context :Proxy.Context, outpoints :List(Data), targetFeerate :Data) -> (result: List(Common.PairInt64(Data)));
calculateCombinedBumpFee @21 (context :Proxy.Context, outpoints :List(Data), targetFeerate :Data) -> (result :Int64, hasResult :Bool);
getPackageLimits @22 (context :Proxy.Context) -> (ancestors :UInt64, descendants :UInt64);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

re: #29409 (comment)

May be deliberate, but the parameters in [...] are 32 bit

Good catch. Updated these. Using a bigger type was harmless but not intentional. (It is a compiler error if you use a smaller type.)

interface ChainNotifications $Proxy.wrap("interfaces::Chain::Notifications") {
destroy @0 (context :Proxy.Context) -> ();
transactionAddedToMempool @1 (context :Proxy.Context, tx :Data) -> ();
transactionRemovedFromMempool @2 (context :Proxy.Context, tx :Data, reason :Int32) -> ();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

re: #29409 (comment)

should we make the param UInt32 like we did one line below for ChainstateRole

Nice find noticing the inconsistency. Both types should be safe because casts between signed and unsigned types are well-defined. I tend to prefer signed types for enums so if the enums use -1, -2, -3 values they will be displayed nicely. But unsigned types could be better for some enums, particularly bitmasks. I updated chainstate role enums variables to be signed for consistency.

schedulerMockForward @7 (context :Proxy.Context, time :Int64) -> ();
}

struct FeeCalculation $Proxy.wrap("FeeCalculation") {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

re: #29409 (comment)

best_height seems to be missing:

Great catch! This is a real bug. Fixed and added comment to the C++ struct to prevent these getting out of sync in the future.

//! Depending on the action returned by the update function, this will either
//! update the setting in memory or write the updated settings to disk.
virtual bool updateRwSetting(const std::string& name, const SettingsUpdate& update_function) = 0;
virtual bool updateRwSetting(const std::string& name, SettingsUpdate update_function) = 0;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

re: #29409 (comment)

Do we have to wait for SettingsValue to be exposed for the other consts to be removed as well?

It looks like const references there could be removed, but did you mean to link to a different interface file? The node.h interface is used by the GUI, unlike the chain.h interface used by wallet. But it could be changed in a separate PR and I noted some other ideas for cleaning up the settings interfaces previously in #30697 (comment) and #30828 (review)

std::any context;
JSONRPCVersion m_json_version = JSONRPCVersion::V1_LEGACY;
// Note: If you add fields to this struct, you should also update the
// JSONRPCRequest struct in ipc/capnp/chain.capnp.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

re: #29409 (comment)

can't we auto-detect that via reflection maybe?

Yes there are different ways that could be detected automatically, not even requiring reflection necessarily. Like you could declare a duplicate struct and assert the sizes of the structs are the same. I think it would actually be possible for the code generator to do this automatically.

I do think the comment here makes sense even with additional enforcement, though since it should be helpful to anyone updating this code or reviewing changes to it.

return T({array.begin(), array.begin() + array.size()});
}

//! Convert base_blob to kj::ArrayPtr.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

re: #29409 (comment)

while ToArray seems to be used mostly for uint256, there are lots of DataStream calls to it which is not base_blob

Thanks took your suggestion and also updated the ToBlob comment

{
// This method is never called because ChainClient::Start is overridden by
// WalletLoader::Start. The custom implementation is needed just because
// the CScheduler& argument this is supposed to pass is not serializable.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

re: #29409 (comment)

is this still accurate?

Yes the ChainClient::start method does still take a scheduler parameter (so the node and wallet can share a scheduler.

virtual void start(CScheduler& scheduler) = 0;

This could probably be changed, but the PR is trying to avoid changing the interface or changing behavior at all.

ryanofsky and others added 4 commits January 6, 2026 05:45
- Add capnp ToBlob, ToArray, Wrap, Serialize, and Unserialize helper functions
- Add support for std::chrono::seconds capnp serialization
- Add support for util::Result capnp serialization
Expose Chain interface to external processes spawning or connecting to
bitcoin-node.
@ryanofsky
Copy link
Contributor Author

ryanofsky commented Jan 6, 2026

Rebased b2cbdd3 -> b7b6d6c (pr/ipc-chain.23 -> pr/ipc-chain.24, compare) due to silent conflict with #30214

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: In progress

Development

Successfully merging this pull request may close these issues.