multiprocess.md raw

Multiprocess Bitcoin Design Document

Guide to the design and architecture of the Bitcoin Core multiprocess feature

This document describes the design of the multiprocess feature. For usage information, see the top-level [multiprocess.md](../multiprocess.md) file.

Introduction

The Bitcoin Core software has historically employed a monolithic architecture. The existing design has integrated functionality like P2P network operations, wallet management, and a GUI into a single executable. While effective, it has limitations in flexibility, security, and scalability. This project introduces changes that transition Bitcoin Core to a more modular architecture. It aims to enhance security, improve usability, and facilitate maintenance and development of the software in the long run.

Current Architecture

The current system features two primary executables: bitcoind and bitcoin-qt. bitcoind combines a Bitcoin P2P node with an integrated JSON-RPC server, wallet, and indexes. bitcoin-qt extends this by incorporating a Qt-based GUI. This monolithic structure, although robust, presents challenges such as limited operational flexibility and increased security risks due to the tight integration of components.

Proposed Architecture

The new architecture divides the existing code into three specialized executables:

This modular approach is designed to enhance security through component isolation and improve usability by allowing independent operation of each module. This allows for new use-cases, such as running the node on a dedicated machine and operating wallets and GUIs on separate machines with the flexibility to start and stop them as needed.

This subdivision could be extended in the future. For example, indexes could be removed from the bitcoin-node executable and run in separate executables. And JSON-RPC servers could be added to wallet and index executables, so they can listen and respond to RPC requests on their own ports, without needing to forward RPC requests through bitcoin-node.

<table><tr><td>

flowchart LR
    node[bitcoin-node] -- listens on --> socket["&lt;datadir&gt;/node.sock"]
    wallet[bitcoin-wallet] -- connects to --> socket
    gui[bitcoin-gui] -- connects to --> socket

</td></tr><tr><td> Processes and socket connection. </td></tr></table>

Component Overview: Navigating the IPC Framework

This section describes the major components of the Inter-Process Communication (IPC) framework covering the relevant source files, generated files, tools, and libraries.

Abstract C++ Classes in `src/interfaces/`

Guidelines](../developer-notes.md#internal-interface-guidelines) to ensure compatibility with Cap'n Proto.

Cap’n Proto Files in `src/ipc/capnp/`

The mpgen Code Generation Tool

C++ Client Subclasses in Generated Code

C++ Server Classes in Generated Code

The libmultiprocess Runtime Library

Type Hooks in `src/ipc/capnp/*-types.h`

Protocol-Agnostic IPC Code in `src/ipc/`

<table><tr><td>

flowchart TD
    capnpFile[ipc/capnp/chain.capnp] -->|Input to| mpgenTool([mpgen Tool])
    mpgenTool -->|Generates| proxyTypesH[ipc/capnp/chain.capnp.proxy-types.h]
    mpgenTool --> proxyClientCpp[ipc/capnp/chain.capnp.proxy-client.c++]
    mpgenTool --> proxyServerCpp[ipc/capnp/chain.capnp.proxy-server.c++]
    proxyTypesH -.->|Includes| interfaces/chain.h
    proxyClientCpp -.-> interfaces/chain.h
    proxyServerCpp -.-> interfaces/chain.h

</td></tr><tr><td> Diagram showing generated source files and includes. </td></tr></table>

Design Considerations

Selection of Cap’n Proto

The choice to use Cap’n Proto for IPC was primarily influenced by its support for passing object references and managing object lifetimes, which would have to be implemented manually with a framework that only supported plain requests and responses like gRPC. The support is especially helpful for passing callback objects like std::function and enabling bidirectional calls between processes.

The choice to use an RPC framework at all instead of a custom protocol was necessitated by the size of Bitcoin Core internal interfaces which consist of around 150 methods that pass complex data structures and are called in complicated ways (in parallel, and from callbacks that can be nested and stored). Writing a custom protocol to wrap these complicated interfaces would be a lot more work, akin to writing a new RPC framework.

Hiding IPC

The IPC mechanism is deliberately isolated from the rest of the codebase so less code has to be concerned with IPC.

Building Bitcoin Core with IPC support is optional, and node, wallet, and GUI code can be compiled to either run in the same process or separate processes. The build system also ensures Cap’n Proto library headers can only be used within the `src/ipc/capnp/` directory, not in other parts of the codebase.

The libmultiprocess runtime is designed to place as few constraints as possible on IPC interfaces and to make IPC calls act like normal function calls. Method arguments, return values, and exceptions are automatically serialized and sent between processes. Object references and std::function arguments are tracked to allow invoked code to call back into invoking code at any time. And there is a 1:1 threading model where every client thread has a corresponding server thread responsible for executing incoming calls from that thread (there can be multiple calls from the same thread due to callbacks) without blocking, and holding the same thread-local variables and locks so behavior is the same whether IPC is used or not.

Interface Definition Maintenance

The choice to maintain interface definitions and C++ type mappings as .capnp files in the `src/ipc/capnp/` was mostly done for convenience, and is probably something that could be improved in the future.

In the current design, class names, method names, and parameter names are duplicated between C++ interfaces in `src/interfaces/` and Cap’n Proto files in `src/ipc/capnp/`. While this keeps C++ interface headers simple and free of references to IPC, it is a maintenance burden because it means inconsistencies between C++ declarations and Cap’n Proto declarations will result in compile errors. (Static type checking ensures these are not runtime errors.)

An alternate approach could use custom C++ Attributes embedded in interface declarations to automatically generate .capnp files from C++ headers. This has not been pursued because parsing C++ headers is more complicated than parsing Cap’n Proto interface definitions, especially portably on multiple platforms.

In the meantime, the developer guide Internal interface guidelines can provide guidance on keeping interfaces consistent and functional and avoiding compile errors.

Interface Stability

The currently defined IPC interfaces are unstable, and can change freely with no backwards compatibility. The decision to allow this stems from the recognition that our current interfaces are still evolving and not yet ideal for external use. As these interfaces mature and become more refined, there may be an opportunity to declare them stable and use Cap’n Proto's support for protocol evolution (Cap'n Proto - Evolving Your Protocol) to allow them to be extended while remaining backwards compatible. This could allow different versions of node, GUI, and wallet binaries to interoperate, and potentially open doors for external tools to utilize these interfaces, such as creating custom indexes through a stable indexing interface. However, for now, the priority is to improve the interfaces internally. Given their current state and the advantages of using JSON-RPC for most common tasks, it's more practical to focus on internal development rather than external applicability.

Security Considerations

The integration of Cap’n Proto and libmultiprocess into the Bitcoin Core architecture increases its potential attack surface. Cap’n Proto, being a complex and substantial new dependency, introduces potential sources of vulnerability, particularly through the creation of new UNIX sockets. The inclusion of libmultiprocess, while a smaller external dependency, also contributes to this risk. However, plans are underway to incorporate libmultiprocess as a git subtree, aligning it more closely with the project's well-reviewed internal libraries. While adopting these multiprocess features does introduce some risk, it's worth noting that they can be disabled, allowing builds without these new dependencies. This flexibility ensures that users can balance functionality with security considerations as needed.

Example Use Cases and Flows

Retrieving a Block Hash

Let’s walk through an example where the bitcoin-wallet process requests the hash of a block at a specific height from the bitcoin-node process. This example demonstrates the practical application of the IPC mechanism, specifically the interplay between C++ method calls and Cap’n Proto-generated RPC calls.

<table><tr><td>

sequenceDiagram
    box "bitcoin-wallet process"
    participant WalletCode as Wallet code
    participant ChainClient as Generated Chain client class<br/>ProxyClient<messages::Chain>
    end
    box "bitcoin-node process"
    participant ChainServer as Generated Chain server class<br/>ProxyServer<messages::Chain>
    participant LocalChain as Chain object<br/>node::ChainImpl
    end

    WalletCode->>ChainClient: getBlockHash(height)
    ChainClient->>ChainServer: Send RPC getBlockHash request
    ChainServer->>LocalChain: getBlockHash(height)
    LocalChain->>ChainServer: Return block hash
    ChainServer->>ChainClient: Send response with block hash
    ChainClient->>WalletCode: Return block hash

</td></tr><tr><td> <code>Chain::getBlockHash</code> call diagram </td></tr></table>

  1. Initiation in bitcoin-wallet

- The wallet process calls the getBlockHash method on a Chain object. This method is defined as a virtual method in `src/interfaces/chain.h`.

  1. Translation to Cap’n Proto RPC

- The Chain::getBlockHash virtual method is overridden by the Chain client subclass to translate the method call into a Cap’n Proto RPC call. - The client subclass is automatically generated by the mpgen tool from the `chain.capnp` file in `src/ipc/capnp/`.

  1. Request Preparation and Dispatch

- The getBlockHash method of the generated Chain client subclass in bitcoin-wallet populates a Cap’n Proto request with the height parameter, sends it to bitcoin-node process, and waits for a response.

  1. Handling in bitcoin-node

- Upon receiving the request, the Cap'n Proto dispatching code in the bitcoin-node process calls the getBlockHash method of the Chain server class. - The server class is automatically generated by the mpgen tool from the `chain.capnp` file in `src/ipc/capnp/`. - The getBlockHash method of the generated Chain server subclass in bitcoin-node receives a Cap’n Proto request object with the height parameter, and calls the getBlockHash method on its local Chain object with the provided height. - When the call returns, it encapsulates the return value in a Cap’n Proto response, which it sends back to the bitcoin-wallet process.

  1. Response and Return

- The getBlockHash method of the generated Chain client subclass in bitcoin-wallet which sent the request now receives the response. - It extracts the block hash value from the response, and returns it to the original caller.

Future Enhancements

Further improvements are possible such as:

Conclusion

This modularization represents an advancement in Bitcoin Core's architecture, offering enhanced security, flexibility, and maintainability. The project invites collaboration and feedback from the community.

Appendices

Glossary of Terms

References

Acknowledgements

This design doc was written by @ryanofsky, who is grateful to all the reviewers who gave feedback and tested multiprocess PRs, and everyone else who's helped with this project. Particular thanks to @ariard who deeply reviewed IPC code and improved the design of the IPC library and initialization process. @jnewbery who championed the early refactoring PRs and helped guide them through development and review. @sjors who has reviewed and repeatedly tested multiprocess code, reporting many issues and helping debug them. @hebasto, @fanquake, and @maflcko who made significant improvements to the build system and fixed countless build issues. @vasild and @jamesob who were brave contributors to the libmultiprocess library. And Chaincode Labs for making this work possible. Also thanks to ChatGPT, who actually wrote most of this document (not @ryanofsky).