Bismuth is a proof-of-work cryptocurrency and smart-contract platform written in Python. A node validates blocks, maintains the ledger, talks to peers, mines, and serves wallets and explorers.
Status — modernization fork. This tree is an in-progress modernization of the Bismuth node. The guiding rule is that consensus does not change: the same blocks must produce the same hashes and validate identically. Every change is replay-verified (the chain is re-hashed end-to-end through a frozen serialization boundary) and gated by the test suite, so storage, networking and APIs can be modernized safely behind a fixed consensus layer. See the Roadmap and
doc/05.For production, use a tagged release —
mainmay be ahead of what's network-validated.
Requires Python 3.8+ (developed/tested on 3.12).
git clone https://github.com/hclivess/bismuth.git && cd bismuth
sudo ./install_node.shinstall_node.sh installs the system + Python dependencies (LMDB, ed25519 with the Python 3.12 build
fix, secp256k1), verifies every import, writes and enables the bismuth-node systemd service and starts
it. A fresh node bootstraps its LMDB ledger from a peer's DB-direct snapshot (doc/43) and syncs the rest.
Flags: --no-start, --restart, --deps-only, --no-apt, --regnet, --tor, --service-name NAME,
--user USER (./install_node.sh --help). Idempotent — re-run it after a git pull.
Upgrading a node whose block store predates the LMDB-only ledger: stop it and run
python3 scripts/upgrade_ledger.py static/blockstore-ledger.db --anchor http://<a synced REST node>:5659
(the node refuses to start on an old schema and prints this command; the installer warns too).
# 1. install dependencies
pip3 install -r requirements-node.txt
# 2. run a node (mainnet)
python3 node.py
# 3. stop it cleanly
python3 node_stop.pyDon't babysit a screen/nohup. Install the node as a systemd service — it survives reboots, restarts
on failure, and stops gracefully (systemd sends SIGTERM, the node finishes its in-flight block and
drains db_lock; every block commit is one atomic LMDB transaction anyway):
sudo ./install_node.sh # deps + service (full installer, above)
sudo bash scripts/install-node-service.sh # service only, deps already installedThe service-only installer auto-detects the repo dir, python3, and user; gracefully stops any node already running
on :5658; writes /etc/systemd/system/bismuth-node.service; and enables + starts it. After that:
systemctl status bismuth-node # up / synced?
systemctl stop bismuth-node # graceful stop
systemctl restart bismuth-node # graceful restart
journalctl -u bismuth-node -f # follow the logsThe service sets BISMUTH_IGNORE_CONFIG_CUSTOM=1 so it always boots mainnet even if a leftover
regnet config_custom.toml is present.
regnet is a private, instantly-mineable chain for development — no peers, no real PoW. The test
suite spins one up automatically:
# run the full test suite (launches a throwaway regnet node, ~90 tests)
python3 -m pytest
# or run a regnet node yourself (REST API on :3031, socket on :3030)
cp tests/config_custom.toml config_custom.toml
python3 node.py regnet2Configuration lives in config.toml (template: config.toml.example; see
doc/11); regnet/test overrides are in tests/config_custom.toml. An existing
config.txt is migrated to config.toml automatically on first start.
A modern, read-only HTTP API runs alongside the legacy socket protocol (opt in with rest_api=True;
default port 5659, regnet 3031). It is self-describing — GET /api (or the bare root /) lists
every method:
| Endpoint | Purpose |
|---|---|
GET /api/status |
node height, peers, difficulty, consensus |
GET /api/capabilities |
REST/transport capabilities for peer sync (reachable = capable) |
GET /api/block/height/{n} · GET /api/block/hash/{h} |
a single block |
GET /api/blocks/since/{h} · GET /api/blocks/range/{a}/{b} |
block ranges, for parallel sync |
GET /api/balance/{address} |
confirmed balance |
GET /api/transaction/{txid} |
a transaction |
GET /api/address/{address}/transactions |
recent txs (?limit=N) |
GET /api/mempool · GET /api/peers |
pending txs · known peers |
Transport compression is applied at the HTTP layer: responses are gzip/br compressed for any
client that sends Accept-Encoding (browsers, and the bundled rest_client.py). Add ?compress=none
to read plaintext, or ?compress=gzip|br to force a codec.
rest_client.py is the parallel block-fetch client: it discovers a peer (GET /api/capabilities) and
pulls /api/blocks/range in concurrent, compressed chunks — the modern alternative to the serial
socket sync. Full REST API reference: doc/15.
| Concern | Where | Notes |
|---|---|---|
| Consensus serialization | bismuth_serialize.py |
frozen signing/block-hash byte forms; the boundary everything hides behind |
| Block processing | digest.py |
validates and applies blocks |
| PoW / difficulty | mining_heavy3.py, difficulty |
Heavy3 hashing |
| Ledger / storage | block_store.py, dbhandler.py, amounts.py, LMDB |
one LMDB environment: blocks, difficulty, signature/address/balance/txid indexes, committed atomically per block |
| Ledger upgrade | scripts/upgrade_ledger.py |
brings an older block store to the current schema (difficulty replay + reindex) |
| Mempool | mempool.py |
pending transactions |
| Networking (legacy) | node.py, connections.py |
length-prefixed JSON over sockets |
| Networking (modern) | rest_api.py, rest_client.py, transport.py |
HTTP API + parallel fetch + compression |
| Crypto / keys | polysign/ |
RSA / ECDSA / ed25519 signers |
Full design docs are in doc/ — start with doc/01-overview.md and the
file reference.
Detailed plan: doc/17-roadmap.md. Database deep-dive: doc/05.
✅ Done
- Frozen consensus boundary — signing-buffer and block-hash byte forms extracted into
bismuth_serialize.pyand characterization-locked, so storage/transport can change without touching consensus. - LMDB-only ledger — one LMDB environment holds the chain, the difficulty record and every index (signature, address history, balances, content txids); a block commits — and a reorg rolls back — in one atomic transaction. LMDB end to end.
- Integer atomic-unit storage — exact integer amounts behind the frozen boundary, replay-verified
(every block hash byte-identical), live on regnet behind
ledger_integer_amounts(default off). - Modern HTTP layer — read-only REST API; capability discovery (reachable = capable); parallel,
gzip/br-compressed block fetch client (
rest_client.py);mainnet0023capability signal.
◑ In progress / next
- Wire the parallel REST fetch into the actual sync path, so nodes catch up over the HTTP API instead of the serial, blocking socket loop.
🗺️ Planned
- Consensus hard fork — sign/hash native integer units + a binary tx encoding (deleting the
'%.8f'/'%.2f'string conversions, all tagged# HARDFORK), and adopt a bounded, content-derived txid (nado-style BLAKE2b, the signature signs the txid). - Deprecate the blocking socket protocol in favour of the HTTP API for node-to-node traffic.
- Repository reorganization — group modules, retire dead files.
- Website: https://bismuth.cz · Explorers: https://bismuth.im
- Hypernodes: https://hypernodes.bismuth.live · https://bismuth.world
- Wallets: download from bismuth.cz · BIS Paper Wallet · Android
- Exchanges: CoinGecko · CoinMarketCap
- Social: Discord · Reddit · Telegram · Blog
- Source: https://github.com/hclivess/Bismuth
GNU General Public License — see LICENSE.