Full stack Bitcoin software

Style guide

The libbitcoin C++ libraries, from Bitcoin primitives to a complete server.

This guide applies to libbitcoin source code. Test code follows it except where Test practices says otherwise.

The code is the specification. Where this guide and the surrounding code disagree, match the surrounding code and raise the discrepancy. Never restyle code you are not otherwise changing.

1. Files#

License header. Every hand-written source file begins with the AGPL header, Copyright (c) 2011-2026 libbitcoin developers, copied verbatim from an existing file. Generated files carry libbitcoin-build's own header (2014-2026 libbitcoin-<repo> developers (see COPYING)); it changes only through regeneration.

Kinds of file#

Kind Location Holds
.hpp include/bitcoin/<repo>/<directory>/ Declarations, plus inline bodies only when short.
.ipp include/bitcoin/<repo>/impl/<directory>/ Template and inline definitions, included at the foot of their .hpp.
.cpp src/<directory>/ Non-template definitions.

Include guards spell the path: LIBBITCOIN_DATABASE_PRIMITIVES_HASHMAP_HPP, and ..._IPP for an .ipp. Do not use #pragma once.

A templated class defines TEMPLATE and CLASS after its declaration, includes the .ipp, then undefines them:

} // namespace database
} // namespace libbitcoin

#define TEMPLATE template <class Link, class Key, size_t RowSize, size_t CellSize>
#define CLASS hashmap<Link, Key, RowSize, CellSize>

#include <bitcoin/database/impl/primitives/hashmap.ipp>

#undef CLASS
#undef TEMPLATE

#endif

In the .ipp, each definition is TEMPLATE on its own line, followed by <return> CLASS::name(...) NOEXCEPT.

Namespaces#

One namespace per line, each closed with a comment. Do not use anonymous namespaces; file-local helpers are static free functions.

namespace libbitcoin {
namespace network {

} // namespace network
} // namespace libbitcoin

Includes#

Headers may not form an inclusion cycle, so the header dependencies of a library form a directed acyclic graph. These rules keep it that way, and apply to every .hpp and .ipp. A .cpp cannot create a cycle, but generally follows them.

  1. Common headers. Each directory has a common header that includes the directory (for example data/data.hpp, log/log.hpp). Code outside the directory includes the common header, never an individual header inside it. Code inside the directory never includes its own common header, which would be a cycle, and includes its siblings explicitly, never assuming one arrives through another.
  2. define.hpp is the library's root common header, included by every header outside the root directory. The root headers are chained into it, and are not included directly anywhere else.
  3. Dependency order. Each library's src/define.cpp lists the order of its root chain and of its directories, in the form /channels : /interfaces /net. Each entry names only its highest dependencies. A cycle is broken by including one file, noted as [sessions/session.hpp], or by a forward declaration, noted as [forward: net].
  4. A .cpp includes its own header first, then a blank line, then the rest. Any standard header comes first, then the libbitcoin headers, sorted, define.hpp among them.
  5. Never include the standard headers that libbitcoin-system's define.hpp provides: <algorithm> <array> <cstddef> <cstdint> <functional> <istream> <ostream> <iostream> <iterator> <limits> <memory> <stdexcept> <string> <string_view> <type_traits> <utility> <variant> <vector>.
  6. Repo aggregates (bitcoin/database.hpp and the like), .vcxproj and .filters files, Makefile.am, configure.ac, and the CMake and CI files are generated by libbitcoin-build. Adding, moving or deleting a source file means regenerating them and committing the result with the change. Never hand-edit them.

2. Naming#

Kind Form Example
Types, functions, variables snake_case get_top_confirmed
Private data members trailing underscore body_, negative_
Type aliases snake_case, a _t suffix allowed data_chunk, settings_t
Shared pointers nested ptr and cptr channel::ptr
Template parameters PascalCase Link, RowSize
Macros UPPER_CASE SUBSCRIBE_ESPLORA
Async completions handle_* handle_receive_post
Platform guards HAVE_*, naming the platform meant HAVE_LINUX, not HAVE_POSIX, for Linux-only code

Vocabulary#

These rules bind identifiers, comments, test names and commit messages. Wire names from other protocols, such as getchaintips or bestblockhash, stay as written.

Use Never
top tip
unspent utxo
output coin (except "coinbase")
input, output vin, vout
output script, input script scriptPubKey, scriptSig
bitcoind (or satoshi in some older references) Core, Bitcoin Core
tx pool (all unconfirmed transactions) mempool, mem_pool

Quick check: grep -nEi '\bcore\b|\bvin\b|\bvout\b|scriptpubkey|scriptsig|\btip\b|\butxo\b|mem_?pool'.

3. Declarations#

enum block_verbosity : size_t
{
    /// Serialized block as hex.
    hex = 0,

    /// Block with txids.
    object = 1
};

if (verbosity == block_verbosity::hex)

Header docs#

A /// comment is a single line saying what the member does, in the style of its siblings. Headers stay lean, with no explanation beyond that. In a member list that has no comments, a new member also goes in bare.

Header section banners use triple slashes:

    /// Setup, not thread safe.
    /// -----------------------------------------------------------------------

    bool create() NOEXCEPT;

Implementation section banners use double slashes and run to column 80:

// not thread safe
// ----------------------------------------------------------------------------

4. Layout#

class BCT_API channel
  : public proxy
{
TEMPLATE
CLASS::hashmap(storage& header, storage& body, const Link& buckets,
    size_t expected) NOEXCEPT
  : head_(header, buckets, expected), body_(body)
{
}
// Right.
item =
{
    messages::peer::unspecified_timestamp,
    messages::peer::service::node_none, address, port
};

// Wrong.
item = { messages::peer::unspecified_timestamp,
    messages::peer::service::node_none, address, port };
if (!is_zero(minimum))
{
    SEND(fee_filter{ minimum }, handle_send, _1);
}

5. Comments#

Default to no comment.

6. Types and numbers#

Never use static_cast for integer conversion. Pick the system cast whose name states what is certain and what is possible:

Cast Size Sign
narrow_cast narrower same
sign_cast not narrower changes
narrow_sign_cast narrower changes
wide_cast wider same
wide_sign_cast wider changes
possible_narrow_cast may narrow same
possible_sign_cast not narrower may change
possible_narrow_sign_cast may narrow changes
possible_sign_narrow_cast narrower may change
possible_narrow_and_sign_cast may narrow may change

Classify the conversion from what the standard fixes, never from the compiler in front of you. For floating point, use to_integer and to_floating. For bool, use to_int.

7. Errors and assertions#

8. Asynchrony and design#

9. Commits#

10. Pre-commit checklist for agents#

Run these against the diff before committing.

  1. Wrapped calls and braces. No argument list is wrapped, and every brace pair balances on one line or vertically.
  2. Comments. Each added comment passes the "written this way from the start" test in section 5. Delete any that narrate the change.
  3. Includes. No forbidden standard headers, no individual header from another directory, and nothing out of dependency order: git diff -U0 | grep '^+#include <', then check each path.
  4. Vocabulary. Run the grep in section 2 over the added lines.
  5. Casts and math. No static_cast< on integers, no std::max or std::min on mixed types, and no == 0: git diff -U0 | grep -E '^\+.*(static_cast<|std::(max|min)\(| == 0\b)'.
  6. Macro bodies. Every macro under a conditional is braced.
  7. NOEXCEPT. Present on every new function.
  8. Generated files. Regenerated through libbitcoin-build if any file was added, moved or deleted.
  9. Tests. Added or updated per Test practices, and the suite passes.
  10. Commit message. One line, a period, no trailer.

Copyright © 2011-2026 libbitcoin developers