Testing & Debugging Bitcoin Applications
Testing Bitcoin applications requires special considerations due to the financial nature of the software and the complexity of the protocol.
Regtest (Recommended for Development)
Regtest provides a completely controlled environment where you can instantly generate blocks. For more on test networks, see Testnets.
Setup:
# Start Bitcoin Core in regtest mode
bitcoind -regtest -daemon
# Create a wallet
bitcoin-cli -regtest createwallet "test"
# Generate initial blocks (need 100+ for spendable coins)
bitcoin-cli -regtest -generate 101
Testing Address Generation
Testing Transaction Construction
Testing with Regtest
Complete Test Flow:
import subprocess
import time
import json
import unittest
class RegtestTestCase(unittest.TestCase):
"""Base test case for Bitcoin regtest integration tests."""
def setUp(self):
# Start bitcoind in regtest mode
subprocess.run(['bitcoind', '-regtest', '-daemon'])
time.sleep(2) # Wait for startup
# Create wallet and generate coins
self.cli('createwallet', 'test')
self.cli('-generate', '101')
def tearDown(self):
# Stop bitcoind
self.cli('stop')
def cli(self, *args):
"""Execute bitcoin-cli command and return parsed result."""
result = subprocess.run(
['bitcoin-cli', '-regtest'] + list(args),
capture_output=True,
text=True
)
if result.stdout:
try:
return json.loads(result.stdout)
except json.JSONDecodeError:
return result.stdout.strip()
return None
def test_send_transaction(self):
# Get a new address
addr = self.cli('getnewaddress')
# Send coins
txid = self.cli('sendtoaddress', addr, '1.0')
# Verify transaction exists
tx = self.cli('gettransaction', txid)
self.assertIsNotNone(tx)
self.assertEqual(tx['amount'], 1.0)
Testing Fee Estimation
def test_fee_estimation(self):
# Generate some blocks with transactions
for _ in range(10):
addr = self.cli('getnewaddress')
self.cli('sendtoaddress', addr, '0.1')
self.cli('-generate', '1')
# Test fee estimation
fee_rate = self.cli('estimatesmartfee', '6')
# Should return a valid fee rate
assert 'feerate' in fee_rate
assert fee_rate['feerate'] > 0
Using bitcoin-cli for Debugging
Inspect Transaction:
# Decode raw transaction
bitcoin-cli decoderawtransaction <hex>
# Get transaction details
bitcoin-cli getrawtransaction <txid> true
# Check mempool
bitcoin-cli getmempoolentry <txid>
Debug Scripts:
# Test script execution (if using btcdeb)
btcdeb '[OP_DUP OP_HASH160 <pubkey_hash> OP_EQUALVERIFY OP_CHECKSIG]'
Analyzing Debug Logs
Bitcoin Core writes detailed logs to debug.log.
Location:
- Linux:
~/.bitcoin/debug.log - macOS:
~/Library/Application Support/Bitcoin/debug.log - Windows:
%APPDATA%\Bitcoin\debug.log
Useful Log Categories:
# Enable specific debug categories
bitcoind -debug=net -debug=mempool -debug=validation
# Or in bitcoin.conf
debug=net
debug=mempool
debug=validation
Common Debug Categories:
net: Network messagesmempool: Mempool operationsvalidation: Block validationrpc: RPC callsestimatefee: Fee estimationselectcoins: Coin selection
Common Debugging Patterns
Transaction Not Confirming:
# Check if in mempool
bitcoin-cli getmempoolentry <txid>
# Check mempool info
bitcoin-cli getmempoolinfo
# Check fee rate
bitcoin-cli getmempoolentry <txid> | jq '.fees.base / .vsize'
Script Verification Failed:
# Decode and inspect the transaction
bitcoin-cli decoderawtransaction <raw_tx>
# Check the referenced output
bitcoin-cli gettxout <prev_txid> <vout>
Using Polar
Polar provides a one-click Lightning Network for testing.
Setup:
- Download from lightningpolar.com
- Create a new network
- Start nodes
- Open channels between nodes
LND Testing
import grpc
import lnrpc
import unittest
class LightningTestCase(unittest.TestCase):
"""Test case for LND Lightning Network integration tests."""
def setUp(self):
# Connect to LND
self.channel = grpc.insecure_channel('localhost:10009')
self.stub = lnrpc.LightningStub(self.channel)
def tearDown(self):
self.channel.close()
def test_create_invoice(self):
# Create invoice
request = lnrpc.Invoice(value=1000, memo="test")
response = self.stub.AddInvoice(request)
# Verify invoice created
self.assertIsNotNone(response.payment_request)
self.assertTrue(response.payment_request.startswith('ln'))
Core Lightning Testing
from pyln.client import LightningRpc
import unittest
class CLightningTestCase(unittest.TestCase):
"""Test case for Core Lightning integration tests."""
def setUp(self):
self.rpc = LightningRpc("/path/to/lightning-rpc")
def test_get_info(self):
info = self.rpc.getinfo()
self.assertIn('id', info)
self.assertIn('alias', info)
Mocking RPC Calls
from unittest.mock import Mock, patch
class TestWithMocks:
@patch('bitcoinrpc.RawProxy')
def test_get_balance(self, mock_proxy):
# Setup mock
mock_proxy.return_value.getbalance.return_value = 10.5
# Test code that uses RPC
rpc = mock_proxy()
balance = rpc.getbalance()
assert balance == 10.5
mock_proxy.return_value.getbalance.assert_called_once()
Mocking Network Responses
import responses
import requests
class TestAPIIntegration:
@responses.activate
def test_fetch_block(self):
# Mock the API response
responses.add(
responses.GET,
'https://blockstream.info/api/block/000000...',
json={'height': 100000, 'tx_count': 50},
status=200
)
# Test code
response = requests.get('https://blockstream.info/api/block/000000...')
data = response.json()
assert data['height'] == 100000
Creating Test Transactions
def create_test_transaction(inputs, outputs):
"""Create a transaction for testing."""
tx = CTransaction()
for txid, vout in inputs:
outpoint = COutPoint(bytes.fromhex(txid)[::-1], vout)
tx.vin.append(CTxIn(outpoint))
for address, amount in outputs:
script = address.to_scriptPubKey()
tx.vout.append(CTxOut(int(amount * 100000000), script))
return tx
Generating Test Keys
import secrets
from bitcoin.wallet import CBitcoinSecret
def generate_test_keypair():
"""Generate a random keypair for testing."""
# Generate random private key
private_key_bytes = secrets.token_bytes(32)
private_key = CBitcoinSecret.from_secret_bytes(private_key_bytes)
# Derive public key
public_key = private_key.pub
return private_key, public_key
GitHub Actions Example
name: Bitcoin Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: Install dependencies
run: |
pip install python-bitcoinlib pytest
- name: Install Bitcoin Core
run: |
wget https://bitcoincore.org/bin/bitcoin-core-25.0/bitcoin-25.0-x86_64-linux-gnu.tar.gz
tar xzf bitcoin-25.0-x86_64-linux-gnu.tar.gz
sudo mv bitcoin-25.0/bin/* /usr/local/bin/
- name: Run unit tests
run: pytest tests/unit/
- name: Run integration tests
run: |
bitcoind -regtest -daemon
sleep 5
pytest tests/integration/
bitcoin-cli -regtest stop
tests/
├── unit/
│ ├── test_transactions.py
│ ├── test_addresses.py
│ └── test_scripts.py
├── integration/
│ ├── test_wallet.py
│ ├── test_mempool.py
│ └── test_mining.py
├── fixtures/
│ ├── transactions.json
│ └── blocks.json
└── conftest.py
- Test Networks - Regtest, Signet, and Testnet setup
- Getting Started - Development environment setup
- Libraries & SDKs - Testing utilities in each library