OpenCBDC is a technical research project focused on answering open questions surrounding central bank digital currencies (CBDCs).
This repository includes the core transaction processor for a hypothetical, general purpose central bank digital currency (CBDC). Initially, this work was derived from Project Hamilton (a collaboration between the MIT Digital Currency Initiative (DCI) and the Federal Reserve Bank of Boston (FRBB)).
For higher-level conceptual explanations, as well as findings and conclusions related to this code, see our research paper.
Initially, we focused our work on achieving high transaction throughput, low latency, and resilience against multiple geographical datacenter outages without significant downtime or any data loss. The design decisions we made to achieve these goals will help inform policy makers around the world about the spectrum of tradeoffs and available options for CBDC design.
NOTE: In cases where there are significant changes to the repository that might need manual intervention down-stream (or other important updates), we will make a NEWS post.
We explored two system architectures for transaction settlement, both based on an unspent transaction output (UTXO) data model and transaction format. Both architectures implement the same schema representing an unspent hash set (UHS) abstraction. One architecture provides linearizability of transactions, whereas the other only provides serializability. By relaxing the ordering constraint, the peak transaction throughput supported by the system scales horizontally with the number of nodes, but the transaction history is unavailable making the system harder to audit retroactively. Both architectures handle multiple geo-distributed datacenter outages with a recovery time objective (RTO) of under ten seconds and a recovery point objective (RPO) of zero.
- "Atomizer" architecture
- Materializes a total ordering of all transactions settled by the system in a linear sequence of batches.
- Requires vertical scaling as peak transaction throughput is limited by the performance of a single system component.
- Maximum demonstrated throughput ~170K transactions per second.
- Geo-replicated latency <2 seconds.
- "Two-phase commit" architecture
- Transaction history is not materialized and only a relative ordering is assigned between directly related transactions.
- Combines two-phase commit (2PC) and conservative two-phase locking (C2PL) to create a system without a single bottlenecked component where peak transaction throughput scales horizontally with the number of nodes.
- Maximum demonstrated throughput ~1.7M transactions per second.
- Geo-replicated latency <1 second.
Read the architecture guide for a detailed description of the system components and implementation of each architecture.
You can sign up to receive updates from technical working groups and to learn more about our work. If you would like to join our technical discussions and help workshop proposals, you can join our Zulip chat.
For more information on how to contribute, please see our Contribution Guide!
- Install Git
- Clone the repository (including submodules)
git clone --recurse-submodules https://github.com/mit-dci/opencbdc-tx
Use these directions if you want to build the source on your machine. If you just want to run the system, see "Run the Code" below.
Ensure your development environment is set correctly for clang:
sudo xcode-select -switch /Library/Developer/CommandLineTools
Or, if you've changed this in the past, you can reset to point to commandline tools with:
sudo xcode-select --reset
- Install dependencies:
brew install leveldb llvm@11 googletest lcov make wget cmake
./scripts/configure.sh
./scripts/build.sh
Note: To run clang-tidy and clang-format (required by lint.sh
), you must add them both to your path.
Ex: ln -s /usr/local/opt/llvm@11/bin/clang-tidy /usr/local/bin/clang-tidy
./scripts/configure.sh
./scripts/build.sh
The easiest way to compile the code and run the system locally is using Docker.
Don't forget to run the docker daemon!
Note: You will need to both run the system and interact with it; you can either use two shells, or you can add the --detach
flag when launching the system (note that it will then remain running till you stop it, e.g., with docker stop
).
Additionally, you can start the atomizer architecture by passing --file docker-compose-atomizer.yml
instead.
The commands below will build a new image every time that you run it.
You can remove the --build
flag after the image has been built to avoid rebuilding.
To run the system with our pre-built image proceed to the next section for the commands to run.
- Run the System
# docker compose --file docker-compose-2pc.yml up --build
- Launch a container in which to run wallet commands (use
--network atomizer-network
instead of--network 2pc-network
if using the atomizer architecture)# docker run --network 2pc-network -ti opencbdc-tx /bin/bash
We publish new docker images for all commits to trunk
.
You can find the images in the Github Container Registry.
Note: You must use docker compose
(not docker-compose
) for this approach to work or you will need to pull the image manually docker pull ghcr.io/mit-dci/opencbdc-tx
.
Additionally, you can start the atomizer architecture by passing --file docker-compose-atomizer.yml --file docker-compose-prebuilt-atomizer.yml
instead.
- Run the system
# docker compose --file docker-compose-2pc.yml --file docker-compose-prebuilt-2pc.yml up --no-build
- Launch a container in which to run wallet commands (use
--network atomizer-network
instead of--network 2pc-network
if using the atomizer architecture)# docker run --network 2pc-network -ti ghcr.io/mit-dci/opencbdc-tx /bin/bash
The following commands are all performed from within the second container we started in the previous step.
In each of the below commands, you should pass atomizer-compose.cfg
instead of 2pc-compose.cfg
if you started the atomizer architecture.
-
Mint new coins (e.g., 10 new UTXOs each with a value of 5 atomic units of currency)
# ./build/src/uhs/client/client-cli 2pc-compose.cfg mempool0.dat wallet0.dat mint 10 5 [2021-08-17 15:11:57.686] [WARN ] Existing wallet file not found [2021-08-17 15:11:57.686] [WARN ] Existing mempool not found 4bc23da407c3a8110145c5b6c38199c8ec3b0e35ea66bbfd78f0ed65304ce6fa
If using the atomizer architecture, you'll need to sync the wallet after:
# ./build/src/uhs/client/client-cli atomizer-compose.cfg mempool0.dat wallet0.dat sync
-
Inspect the balance of a wallet
# ./build/src/uhs/client/client-cli 2pc-compose.cfg mempool0.dat wallet0.dat info Balance: $0.50, UTXOs: 10, pending TXs: 0
-
Make a new wallet
# ./build/src/uhs/client/client-cli 2pc-compose.cfg mempool1.dat wallet1.dat newaddress [2021-08-17 15:13:16.148] [WARN ] Existing wallet file not found [2021-08-17 15:13:16.148] [WARN ] Existing mempool not found usd1qrw038lx5n4wxx3yvuwdndpr7gnm347d6pn37uywgudzq90w7fsuk52kd5u
-
Send currency from one wallet to another (e.g., 30 atomic units of currency)
# ./build/src/uhs/client/client-cli 2pc-compose.cfg mempool0.dat wallet0.dat send 30 usd1qrw038lx5n4wxx3yvuwdndpr7gnm347d6pn37uywgudzq90w7fsuk52kd5u tx_id: cc1f7dc708be5b07e23e125cf0674002ff8546a9342928114bc97031d8b96e75 Data for recipient importinput: cc1f7dc708be5b07e23e125cf0674002ff8546a9342928114bc97031d8b96e750000000000000000d0e4f689b550f623e9370edae235de50417860be0f2f8e924eca9f402fcefeaa1e00000000000000 Sentinel responded: Confirmed
If using the atomizer architecture, you'll need to sync the sending wallet after:
# ./build/src/uhs/client/client-cli atomizer-compose.cfg mempool0.dat wallet0.dat sync
-
Check that the currency is no longer available in the sending wallet
# ./build/src/uhs/client/client-cli 2pc-compose.cfg mempool0.dat wallet0.dat info Balance: $0.20, UTXOs: 4, pending TXs: 0
-
Import coins to the receiving wallet
# ./build/src/uhs/client/client-cli 2pc-compose.cfg mempool1.dat wallet1.dat importinput cc1f7dc708be5b07e23e125cf0674002ff8546a9342928114bc97031d8b96e750000000000000000d0e4f689b550f623e9370edae235de50417860be0f2f8e924eca9f402fcefeaa1e00000000000000 # ./build/src/uhs/client/client-cli 2pc-compose.cfg mempool1.dat wallet1.dat sync # ./build/src/uhs/client/client-cli 2pc-compose.cfg mempool1.dat wallet1.dat info Balance: $0.30, UTXOs: 1, pending TXs: 0
Running Unit & Integration Tests
- Build the container
# docker build . -t opencbdc-tx
- Run Unit & Integration Tests
# docker run -ti opencbdc-tx ./scripts/test.sh