Skip to content

recorner/casini

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

⛏️ Casini

Bitcoin Testnet4 Solo CPU Miner

Exploit BIP-94's 20-minute difficulty exception to mine testnet4 blocks with nothing but a CPU.

Bitcoin Core Docker License Testnet4


How It Works

Testnet4 (BIP-94) has a 20-minute difficulty exception: when a block's timestamp exceeds the previous block's timestamp by more than 1200 seconds, difficulty resets to 1 — making CPU mining viable.

Casini exploits this by crafting blocks with future timestamps (up to the consensus limit of now + 7200s) that trigger the difficulty-1 exception, then grinding SHA256d nonces at ~5 MH/s until a valid proof-of-work is found.

                        Consensus Window
     ◄───────────────────────────────────────────►
     │                                           │
 prev_time          prev_time + 1201        now + 7200
     │                    │                      │
     ▼                    ▼                      ▼
─────┼────────────────────┼──────────────────────┼─────► time
     │   normal diff      │     difficulty = 1   │
     │                    │     (BIP-94 rule)     │
     │                    │          ▲            │
     │                    │          │            │
     │                    │    block timestamp    │
     │                    │    set here           │
     │                    │                       │
     │                    ├───── mine window ─────┤
     │                    │                       │
                          │◄─── wait then ───────►│
                          │     submit when       │
                          │     ts < now + 7200   │

Chain Mining Strategy

Rather than mining one block and waiting idle, Casini builds chains of blocks during the wait period:

 Template (mempool txs)        Coinbase-only
┌──────────────┐          ┌──────────────┐
│   Block 0    │──parent──│   Block 1    │
│  height N    │  hash    │  height N+1  │
│  228 txs     │          │  1 tx        │
│  ts=T        │          │  ts=T+1201   │
└──────────────┘          └──────────────┘
       │                         │
   grind nonce              grind nonce
   during wait              during wait
       │                         │
       ▼                         ▼
   submit when              submit when
   T < now+7200             T+1201 < now+7200

Block 0 is built from getblocktemplate and includes mempool transactions (higher reward). Child blocks are coinbase-only, each with ts = parent_ts + 1201 to satisfy the BIP-94 rule. All nonces are ground in parallel while waiting for the timestamp windows to open, then blocks are submitted sequentially as each window arrives.

Architecture

┌─────────────────────────────────────────────────────────────────┐
│                       Docker Compose                            │
│                                                                 │
│  ┌─────────────────────┐          ┌─────────────────────────┐  │
│  │    bitcoin-node      │          │         miner            │  │
│  │                      │          │                          │  │
│  │  Bitcoin Core v30.2  │◄──RPC───│  mine.sh (orchestrator)  │  │
│  │  Testnet4 full node  │  :48332 │         │                │  │
│  │                      │         │         ▼                │  │
│  │  ┌────────────────┐  │         │  ┌──────────────────┐   │  │
│  │  │ Wallet: mining │  │         │  │  cpu_miner.py    │   │  │
│  │  │ (auto-created) │  │         │  │  Chain builder   │   │  │
│  │  └────────────────┘  │         │  │  Block assembler │   │  │
│  │                      │         │  │  Submit logic    │   │  │
│  │  Ports:              │         │  └───────┬──────────┘   │  │
│  │  48332 RPC (local)   │         │          │              │  │
│  │  48333 P2P (public)  │         │          ▼              │  │
│  │  28332 ZMQ blocks    │         │  ┌──────────────────┐   │  │
│  │  28333 ZMQ txs       │         │  │ nonce_grinder    │   │  │
│  └─────────────────────┘          │  │ 8-thread C       │   │  │
│                                    │  │ SHA256d scanner  │   │  │
│                                    │  │ ~5.36 MH/s       │   │  │
│                                    │  │ midstate optim.  │   │  │
│                                    │  └──────────────────┘   │  │
│                                    └─────────────────────────┘  │
└─────────────────────────────────────────────────────────────────┘

Mining Pipeline

┌─────────────┐    ┌──────────────┐    ┌──────────────┐    ┌──────────────┐    ┌──────────────┐
│  Get Block  │    │  Build Block │    │ Grind Nonce  │    │ Wait for     │    │   Submit     │
│  Template   │───►│  Header +    │───►│ (8 threads,  │───►│ Timestamp    │───►│   Block      │
│  (RPC)      │    │  Coinbase TX │    │  midstate)   │    │ Window       │    │   (RPC)      │
└─────────────┘    └──────────────┘    └──────────────┘    └──────────────┘    └──────────────┘
                                              │                    │
                                              │ stale-tip check    │ stale-tip check
                                              │ every 5s           │ every 5s
                                              ▼                    ▼
                                        abort if chain       abort if chain
                                        outpaced             outpaced

Sample Output

Here's what a typical mining session looks like:

[2026-03-27 16:00:19] ============================================
[2026-03-27 16:00:19]  Casini — Testnet4 CPU Miner
[2026-03-27 16:00:19]  Target: 500 BTC
[2026-03-27 16:00:19] ============================================
[2026-03-27 16:00:19] Waiting for bitcoin node RPC to become available...
[2026-03-27 16:00:19] Node RPC is available.
[2026-03-27 16:00:19] Initial block download complete.
[2026-03-27 16:00:19] Setting up mining wallet 'mining'...
[2026-03-27 16:00:19] Reusing mining address: tb1pqk0mq7jh6y5pfzfq460ufl6gtdeaankwcpc5cyrewc564qfjjc0s9qvzzy
[2026-03-27 16:00:19] --- Status ---
[2026-03-27 16:00:19]   Block height:      127539
[2026-03-27 16:00:19]   Blocks mined:      0
[2026-03-27 16:00:19]   Spendable balance: 0.00628087 BTC
[2026-03-27 16:00:19]   Immature balance:  350.00888976 BTC
[2026-03-27 16:00:19]   Target:            500 BTC
[2026-03-27 16:00:19] Mining 1 block(s)...
[2026-03-27 16:00:19]   === Mining round 1 (mined 0/1) ===
[2026-03-27 16:00:19]     Chain mode: first submit in 698s (height 127540)
[2026-03-27 16:00:19]     [0] Height 127540, 228 txs, 50.00435893 BTC, ts=1774635118
                          Grinding with 8 threads, midstate optimization...
                          T0: 10M nonces
                          T3: 10M nonces
                          ...
[2026-03-27 16:02:44]     [0] Found nonce: 82941023
[2026-03-27 16:02:44]     [1] Height 127541, 1 tx, 50.00000000 BTC, ts=1774636319
                          Grinding with 8 threads, midstate optimization...
[2026-03-27 16:05:11]     [1] Found nonce: 194820437
[2026-03-27 16:05:11]     Chain ready: 2 block(s), heights 127540-127541
[2026-03-27 16:11:38]     [0] Submitting 00000000a1b2c3d4... (height 127540)
[2026-03-27 16:11:38]     *** Block ACCEPTED! Hash: 00000000a1b2c3d4e5f6... ***
[2026-03-27 16:31:39]     [1] Submitting 00000000f9e8d7c6... (height 127541)
[2026-03-27 16:31:39]     *** Block ACCEPTED! Hash: 00000000f9e8d7c6b5a4... ***

Quick Start

Prerequisites

  • Docker & Docker Compose
  • 2+ CPU cores (8 recommended)
  • 4 GB RAM minimum
  • 10 GB disk space

1. Clone and configure

git clone https://github.com/recorner/casini.git
cd casini

# Generate RPC credentials
python3 scripts/gen-rpcauth.py

# Paste rpcauth= line into bitcoin-node/bitcoin.conf
# Copy credentials into .env
cp .env.example .env
# Edit .env with the generated password

Never commit .env — it is gitignored by default.

2. Build and start the node

docker compose build bitcoin-node   # ~15-20 min (compiles Bitcoin Core from source)
docker compose up -d bitcoin-node

3. Wait for sync

chmod +x scripts/*.sh
./scripts/wait-for-sync.sh

# Or check manually
docker exec casini-bitcoin-node bitcoin-cli -datadir=/home/bitcoin/.bitcoin getblockchaininfo

4. Start mining

docker compose build miner
docker compose up -d miner

# Watch mining progress
docker compose logs -f miner

The miner automatically:

  • Creates a mining wallet (Taproot/bech32m address)
  • Detects whether generatetoaddress or the custom cpu_miner should be used
  • Builds chains of blocks to maximize throughput
  • Tracks orphaned blocks and reports live balance

5. Check balances

./scripts/check-balance.sh

Component Details

nonce_grinder.c — SHA256d Nonce Scanner

A multi-threaded C program that performs the proof-of-work search:

  • Midstate optimization: Pre-computes SHA256 state for the first 64 bytes of the header, only processes the final 16 bytes + nonce per iteration
  • 8 parallel threads scanning non-overlapping nonce ranges (0 to 2³²)
  • ~670 KH/s per thread, ~5.36 MH/s total on an 8-core Xeon
  • Finds a difficulty-1 nonce in 2-13 minutes on average
  • Compiled with gcc -O3 -march=native
Header (80 bytes)
├─ bytes 0-63 ──► SHA256 midstate (precomputed once)
└─ bytes 64-79 ──► last 16 bytes iterated with nonce
                    │
                    ├─ Thread 0: nonce 0x00000000 – 0x1FFFFFFF
                    ├─ Thread 1: nonce 0x20000000 – 0x3FFFFFFF
                    ├─ ...
                    └─ Thread 7: nonce 0xE0000000 – 0xFFFFFFFF

cpu_miner.py — Chain Mining Coordinator

Python script that orchestrates the full mining pipeline:

  1. getblocktemplate — fetches mempool transactions and block parameters
  2. Build coinbase TX — segwit coinbase with witness commitment
  3. Construct block header — version, prev hash, merkle root, timestamp, nBits
  4. Grind nonce — calls nonce_grinder binary, monitors for stale tips
  5. Build child blocks — coinbase-only blocks using parent hash
  6. Submit chain — waits for each timestamp window, submits sequentially

Key features:

  • Stale-tip detection every 5s during grinding (aborts if chain outpaced)
  • Extra-nonce rotation to avoid nonce-space exhaustion
  • Configurable MAX_CHAIN depth (default: 2)

mine.sh — Mining Orchestrator

Bash wrapper that manages the mining lifecycle:

  • Node readiness checks and IBD (Initial Block Download) monitoring
  • Wallet auto-creation and address management
  • Smart routing: generatetoaddress when real-time is ahead, cpu_miner when timestamps are in the future
  • Live balance reporting (spendable + immature)
  • Orphan detection — checks all mined block hashes for -1 confirmations

Configuration

Environment Variables

Variable Default Description
BITCOIN_RPC_HOST bitcoin-node Node hostname
BITCOIN_RPC_PORT 48332 RPC port
BITCOIN_RPC_USER casini RPC username
BITCOIN_RPC_PASSWORD (required) RPC password
MINING_TARGET_BTC 500 Target BTC balance
MINING_WALLET mining Wallet name
BLOCKS_PER_BATCH 1 Blocks per mining cycle

Network Ports

Port Protocol Binding Purpose
48332 RPC 127.0.0.1 Bitcoin Core JSON-RPC
48333 P2P 0.0.0.0 Peer discovery
28332 ZMQ 127.0.0.1 Raw block notifications
28333 ZMQ 127.0.0.1 Raw transaction notifications

Scale Testing

Casini includes scripts for wallet/payment system testing after mining:

# Create and fund test wallets (sender, receiver, merchant, customer)
./scripts/setup-wallet.sh

# Run load test: 2 TPS for 5 minutes with auto-mining every 30s
./scripts/generate-load.sh

# Customize parameters
./scripts/generate-load.sh --tps 5 --duration 600 --amount 0.0001 --mine-interval 15

Features:

  • Round-robin sends between test wallets
  • Auto-mines blocks at configurable intervals
  • Real-time metrics (actual TPS, mempool depth, failure rate)
  • Final summary report

File Structure

casini/
├── docker-compose.yml              # Service orchestration
├── .env.example                    # Template for RPC credentials
├── bitcoin-node/
│   ├── Dockerfile                  # Multi-stage Bitcoin Core v30.2 build
│   ├── .dockerignore
│   └── bitcoin.conf                # Testnet4 node configuration
├── miner/
│   ├── Dockerfile                  # Multi-stage build (node-bins → grinder → runtime)
│   ├── .dockerignore
│   ├── mine.sh                     # Mining orchestrator (bash)
│   ├── cpu_miner.py                # Chain mining coordinator (python)
│   └── nonce_grinder.c             # Multi-threaded SHA256d scanner (C)
├── scripts/
│   ├── gen-rpcauth.py              # Generate RPC credentials
│   ├── wait-for-sync.sh            # IBD completion monitor
│   ├── setup-wallet.sh             # Create + fund test wallets
│   ├── generate-load.sh            # Transaction load generator
│   └── check-balance.sh            # Balance reporter
└── README.md

Technical Notes

Why Timestamps Are in the Future

Testnet4 block timestamps run ~6000-7000s ahead of real time. The BIP-94 20-minute rule requires ts > prev_time + 1200, but the MAX_FUTURE_BLOCK_TIME consensus limit caps timestamps at now + 7200. This creates a mandatory wait window: blocks must be ground first, then held until block_time - 7200 < now before submission.

Orphan Risk

Other miners compete on the same chain. When you submit a block, someone else may have already advanced the tip. Block 0 of each chain has a high acceptance rate (~70-80%); child blocks (block 1, 2+) face increasing orphan rates due to the additional ~1201s wait per child. This is why MAX_CHAIN is set to 2.

Coinbase Maturity

Mined block rewards require 100 confirmations before they become spendable. On testnet4 at ~1 block per 20 minutes, that's roughly 33 hours per reward to mature.

Troubleshooting

Node won't start

docker compose logs bitcoin-node

"Difficulty may be high" messages

Normal behavior — testnet4's 20-minute rule means occasional wait periods when real difficulty is active. The miner handles the transition automatically.

Coins shown as "immature"

Coinbase rewards require 100 confirmations. Keep mining — each new block matures earlier rewards.

Block accepted then orphaned

Common in competitive mining. The miner detects orphans automatically and logs warnings. Chain mining reduces orphan risk by submitting connected blocks that resist reorgs.

Nonce space exhaustion

If the grinder exhausts all 2³² nonces without finding a valid hash (~37% chance at difficulty 1), cpu_miner.py rotates the extra_nonce in the coinbase and retries with a new merkle root.

License

MIT

Releases

No releases published

Packages

 
 
 

Contributors