# Running a node What `pip install btclib-node` puts on `PATH`, run against each of the four chains; pointing it at a peer of your own; reading its progress while it syncs; the RPC methods it answers; what it validates and what it does not; and what a mainnet sync has actually been measured to cost. `README.md` and `SECURITY.md` are the other two pages a new reader wants first — this one is what comes after `pip install`. ## Starting a node ```shell pip install btclib-node btclib-node -h ``` `btclib-node -h` lists every flag, spelled the way Bitcoin Core spells its own — a single dash, `-datadir` rather than `--datadir`, though the double-dash spelling is accepted too. One flag selects the chain: ```shell btclib-node # mainnet, the default btclib-node -testnet btclib-node -signet btclib-node -regtest ``` Data lives under `-datadir=` (`~/.btclib` if it is not given), inside a subdirectory named for the chain — `/mainnet`, `/testnet`, `/signet`, `/regtest` — so the four chains never share one store and switching between them on the same `-datadir` loses nothing. An operator's existing `bitcoin.conf` is read from the data directory without being told to, `-conf=` naming another one; a `[section]` per chain, `main` included, plus one default section that applies to every chain, is the same shape Core's own reader uses, and the command line always wins over the file. `-prune` is accepted, and `## Pruning` below is what any nonzero value actually does. ## Pruning `-prune=` matches Core's own three-way split. `` of `1` is manual pruning: nothing is deleted on its own, and the `pruneblockchain` RPC is what deletes block and undo data up to a height or a timestamp, both answering the height of the last block actually pruned. `` at or above `550` is automatic pruning to roughly `` MiB on disk, tracked against actual bytes under `blocks/` and re-checked after every block this node connects. Neither ever reaches within the last 288 blocks (about two days) of the tip -- `getblockchaininfo`'s own `pruned`, `pruneheight`, `automatic_pruning` and `prune_target_size` answer for what a caller wants to check programmatically, `pruneheight` being the first height still on disk. `` from `2` to `549` refuses to start, Core's own wording -- too small a target to actually run a node on. A negative `` refuses to start too, rather than silently pruning. A pruned node tells a peer so: `NODE_NETWORK_LIMITED` on its own `version`, `NODE_NETWORK` dropped, matching what Core's own pruned node advertises. `getrawtransaction`'s block-hash lookup and a peer's own `getdata` both answer for a block this node has since deleted the way Core's peer does -- `"Block not available (pruned data)"` over RPC, no answer at all, or the connection dropped outright, over the wire. `-connect`/`-addnode` still dial whatever is named; a pruned node serving another pruned node is between the two operators to arrange, same as it is for Core. ## Pointing it at a peer of your own `-connect=[:port]`, repeatable, dials only the peers named this way. Naming one turns off DNS seeding and every automatically-drawn outbound connection — Core's own `-connect` interaction — and defaults `-listen` off too, unless `-listen`/`-nolisten` overrides it explicitly. `-addnode=[:port]` dials a peer alongside the ordinary draw rather than instead of it. Only a literal IP address is accepted in either; a hostname is refused rather than resolved ([#573](https://github.com/btclib-org/btclib-node/issues/573)). This is the answer to "I already have the chain on another node, can this one validate it against that copy instead of the internet": ```shell btclib-node -regtest -datadir= -rpcport= \ -connect=127.0.0.1: ``` ## Reading progress `getblockchaininfo` answers `headers` (the height of the best header chain this node knows, downloaded or not) and `blocks` (the height of the chain it has fully validated) as two separate members, plus `initialblockdownload`, a boolean matching Core's own definition — chain work against a minimum and the tip's own age against a staleness bound, not merely "out of candidates to try right now." `getblockcount` answers `blocks` alone, so it does not move during header sync: `headers` is what to watch while the chain of headers extends ahead of the blocks arriving behind it. `verificationprogress` is deliberately absent rather than answered wrong — Core estimates it from a per-chain assumed transaction rate, and neither that assumption nor the per-block count it is checked against exists in this tree yet ([#575](https://github.com/btclib-org/btclib-node/issues/575), which added the other members above). The listener authenticates nothing (`SECURITY.md`), so a call needs no credential: ```shell curl -s http://127.0.0.1: \ -d '{"jsonrpc":"2.0","id":"1","method":"getblockchaininfo"}' ``` ## RPC methods Fourteen, each mirroring the Core method of the same name: `getbestblockhash`, `getblockcount`, `getblockchaininfo`, `getblockhash`, `getblockheader`, `getpeerinfo`, `getconnectioncount`, `getmempoolinfo`, `getrawmempool`, `getrawtransaction`, `testmempoolaccept`, `sendrawtransaction`, `ping`, `stop`. ## What is validated, and what is not Every header's proof of work, and its retarget and median-time-past against its ancestors; a block's own structure against its difficulty bound, on receipt; every script and every signature in it; a coinbase that pays no more than subsidy plus fees and commits to its own height under BIP34 ([#568](https://github.com/btclib-org/btclib-node/issues/568) and [#571](https://github.com/btclib-org/btclib-node/issues/571)); a spend of a coinbase not yet `COINBASE_MATURITY` deep ([#569](https://github.com/btclib-org/btclib-node/issues/569)); the same coinbase landing twice, BIP30 ([#570](https://github.com/btclib-org/btclib-node/issues/570)); and a transaction's own `nLockTime` together with its BIP68 relative lock ([#572](https://github.com/btclib-org/btclib-node/issues/572)). All five of those were open questions this tracker carried and are now closed. Pruning is accepted as a flag and refused rather than honoured — see *Starting a node* above ([#601](https://github.com/btclib-org/btclib-node/issues/601)). The UTXO set carries no commitment a caller can audit against ([#639](https://github.com/btclib-org/btclib-node/issues/639)), and this node answers no `gettxoutsetinfo` of its own. ## What a sync costs The first initial block download this tree has measured ([#576](https://github.com/btclib-org/btclib-node/issues/576)). Machine throughout: darwin 25.6.0, ten cores, CPython 3.14.6 (GIL build), `btclib-secp256k1` installed. The peer for the header sync and the block-validation figures was a local, fully-synced `bitcoind` dialled over loopback as the only peer, `-connect`, DNS seeds off — none of it crossed the internet. ### Header sync Measured 2026-08-27 at `origin/main` `8dfd9d82b5520547cf8df3e4c8b9c229264a7889`, load 3.7 and 5.3: 964,357 headers — the peer's own height plus genesis — in 40.1 seconds, twice, to the tenth of a second, every header's proof of work and retarget arithmetic checked, no checkpoint and nothing assumed valid. ### Block validation Measured the same day, `origin/main` `7da40ebcd606a7d3ef09fe2a49a2d6d63017c8b1`. Six populated blocks up to mainnet height 964,000, their prevouts pulled from the peer over RPC and handed to the same call `update_chain` itself makes (`interpreter.check_transactions`, no download and nothing written): roughly 40,000 to 65,000 inputs validated a second, at a load around 3.0 — about 200 microseconds per input per core, one libsecp256k1 verification and the script engine around it. A control against the same block with one prevout's script swapped for another's was refused. A second range — the emptiest 136,000 blocks from genesis, downloaded and validated for 750 seconds and stopped by hand — showed a blocks-per-second rate falling threefold, but it correlates with the machine's own rising load at -0.68 across that same run and is not offered here as an independent cost figure; the store measurement below, taken under a load held level, is what that falling rate was actually showing. ### The store Measured 2026-08-28 at `origin/main` `0f33f69d711169cab8a2431a1cce5a83c65fc65a`, against this node's `KeyValueStore` filled to a range of sizes and then given one mainnet block's own reads, deletes and writes — **on the sqlite3 store this tree used at the time**; the store has been RocksDB since [#641](https://github.com/btclib-org/btclib-node/issues/641), and this figure has not been re-measured against it, there being no committed script in this tree to rerun. Below about four million rows the store is write-bound and the cost per operation is nearly flat; a cliff sits between eight and sixteen million rows, where reads and writes both jump because the working set stops fitting the page cache — measured at 2.9 to 16.2 microseconds an operation at eight million rows, and 29.3 to 55.0 microseconds at sixteen million. The peer's own `gettxoutsetinfo` put the real UTXO set at 165,770,908 outputs, about ten times the largest size measured here, so every figure below is a floor and not a forecast. Spends were drawn uniformly across the whole key space, the worst case a chain can present; drawing 90% of them from the newest 1% of keys — the locality a real chain has and this measurement otherwise ignores — bought 8 to 15%, not the gap between this floor and a real cost. ### What the whole chain would cost A sampled input count — 193 samples of `getblockstats`, mean 3,655.9 inputs a block — puts the chain at roughly 3.5 billion inputs and 7.2 billion store deletes-and-writes. At the input rate above, script validation alone is on the order of a day, spread across ten cores. The store is the larger half and it is single-threaded: at the eight-million-row cost above it comes to about 1.5 days, and at the sixteen-million-row cost, about 5.8 days — both measured on a store a tenth the size a real sync's store reaches, so both are floors on the true figure rather than an estimate of it.