Signet & Testnet Deep Dive
Test networks are essential for Bitcoin development. This guide covers the differences between testnet, signet, and regtest, along with setup instructions and best practices.
Network Comparison
| Feature | Mainnet | Testnet | Signet | Regtest |
|---|---|---|---|---|
| Real Value | Yes | No | No | No |
| Block Time | ~10 min | Variable | ~10 min | Instant |
| Difficulty | Dynamic | Dynamic | Fixed | Minimal |
| Public | Yes | Yes | Yes | Local |
| Controlled | No | No | Yes | Yes |
| Best For | Production | Integration | Testing | Unit tests |
Testnet (Testnet3)
Overview
Testnet is the original Bitcoin test network. It mirrors mainnet but with worthless coins.
Configuration
# bitcoin.conf
testnet=1
[test]
rpcuser=user
rpcpassword=password
rpcport=18332
Starting Testnet
# Start testnet node
bitcoind -testnet -daemon
# Use bitcoin-cli with testnet
bitcoin-cli -testnet getblockchaininfo
# Check sync status
bitcoin-cli -testnet getblockcount
Testnet Faucets
Get free testnet coins:
Testnet Explorers
Testnet Challenges
Issues with Testnet:
- Block times can be erratic (difficulty resets)
- Occasional "block storms" (many blocks quickly)
- May have long periods with no blocks
- Reorgs more common than mainnet
Signet
Overview
Signet (BIP-325) provides a more controlled test environment. Blocks are signed by specific keys, ensuring predictable block production.
Benefits Over Testnet
- Predictable Blocks: Consistent ~10 minute block times
- No Difficulty Resets: Stable mining simulation
- Controlled Environment: No surprise reorgs
- Better for Testing: More mainnet-like behavior
Default Signet Configuration
# bitcoin.conf
signet=1
[signet]
rpcuser=user
rpcpassword=password
rpcport=38332
Starting Signet
# Start signet node
bitcoind -signet -daemon
# Check status
bitcoin-cli -signet getblockchaininfo
# Get network info
bitcoin-cli -signet getnetworkinfo
Signet Faucets
Signet Explorer
Custom Signet
You can create your own signet for private testing:
# Generate signing key
bitcoin-cli -regtest createwallet "signet_signer"
SIGNET_ADDR=$(bitcoin-cli -regtest getnewaddress)
SIGNET_PUBKEY=$(bitcoin-cli -regtest getaddressinfo $SIGNET_ADDR | jq -r '.pubkey')
# Create signet challenge script
# OP_1 <pubkey> OP_1 OP_CHECKMULTISIG
CHALLENGE="5121${SIGNET_PUBKEY}51ae"
# Custom signet bitcoin.conf
signet=1
signetchallenge=5121...your_challenge...51ae
[signet]
rpcport=38332
Regtest (Regression Test)
Overview
Regtest is a local, private blockchain perfect for development and automated testing.
Configuration
# bitcoin.conf
regtest=1
[regtest]
rpcuser=user
rpcpassword=password
rpcport=18443
Starting Regtest
# Start regtest node
bitcoind -regtest -daemon
# Create wallet
bitcoin-cli -regtest createwallet "dev"
# Generate initial coins (need 100+ blocks for maturity)
bitcoin-cli -regtest -generate 101
# Check balance
bitcoin-cli -regtest getbalance
Instant Block Generation
# Generate blocks on demand
bitcoin-cli -regtest -generate 1
# Generate to specific address
bitcoin-cli -regtest generatetoaddress 1 "bcrt1q..."
# Generate multiple blocks
bitcoin-cli -regtest -generate 10
Regtest for Testing
import subprocess
import json
import time
class RegtestNode:
def __init__(self, datadir=None):
self.datadir = datadir or '/tmp/regtest'
def start(self):
subprocess.run([
'bitcoind', '-regtest', '-daemon',
f'-datadir={self.datadir}',
'-rpcuser=test', '-rpcpassword=test'
])
time.sleep(2)
def stop(self):
self.cli('stop')
def cli(self, *args):
result = subprocess.run(
['bitcoin-cli', '-regtest',
f'-datadir={self.datadir}',
'-rpcuser=test', '-rpcpassword=test'] + list(args),
capture_output=True, text=True
)
if result.stdout:
try:
return json.loads(result.stdout)
except:
return result.stdout.strip()
return None
def generate(self, blocks=1):
return self.cli('-generate', str(blocks))
def get_new_address(self):
return self.cli('getnewaddress')
def send(self, address, amount):
return self.cli('sendtoaddress', address, str(amount))
# Usage
node = RegtestNode()
node.start()
node.cli('createwallet', 'test')
node.generate(101) # Mine initial coins
addr = node.get_new_address()
txid = node.send(addr, 1.0)
node.generate(1) # Confirm transaction
node.stop()
Lightning Network Testing
Polar (Recommended)
Polar provides a GUI for managing Lightning test networks.
# Download from https://lightningpolar.com/
# Create network with:
# - Bitcoin Core (regtest)
# - LND / Core Lightning / Eclair nodes
# - Automatic channel opening
Manual LND Setup on Regtest
# Start Bitcoin Core regtest
bitcoind -regtest -daemon -zmqpubrawblock=tcp://127.0.0.1:28332 -zmqpubrawtx=tcp://127.0.0.1:28333
# Generate initial blocks
bitcoin-cli -regtest -generate 101
# Start LND
lnd --bitcoin.active --bitcoin.regtest \
--bitcoin.node=bitcoind \
--bitcoind.rpcuser=user --bitcoind.rpcpass=password \
--bitcoind.zmqpubrawblock=tcp://127.0.0.1:28332 \
--bitcoind.zmqpubrawtx=tcp://127.0.0.1:28333
# Create wallet and get address
lncli --network=regtest create
lncli --network=regtest newaddress p2wkh
# Fund the wallet (from Bitcoin Core)
bitcoin-cli -regtest sendtoaddress <lnd_address> 1
bitcoin-cli -regtest -generate 6
Core Lightning on Regtest
# Start lightningd
lightningd --network=regtest \
--bitcoin-rpcuser=user \
--bitcoin-rpcpassword=password \
--bitcoin-rpcport=18443
# Get new address
lightning-cli --network=regtest newaddr
# Fund and open channel
lightning-cli --network=regtest connect <node_id>@localhost:9735
lightning-cli --network=regtest fundchannel <node_id> 100000
Automated Testing Setup
Docker Compose Example
# docker-compose.yml
version: '3.8'
services:
bitcoind:
image: ruimarinho/bitcoin-core:latest
command:
- -regtest
- -rpcuser=test
- -rpcpassword=test
- -rpcallowip=0.0.0.0/0
- -rpcbind=0.0.0.0
- -zmqpubrawblock=tcp://0.0.0.0:28332
- -zmqpubrawtx=tcp://0.0.0.0:28333
ports:
- "18443:18443"
- "28332:28332"
- "28333:28333"
volumes:
- bitcoin_data:/home/bitcoin/.bitcoin
lnd:
image: lightninglabs/lnd:latest
depends_on:
- bitcoind
command:
- --bitcoin.active
- --bitcoin.regtest
- --bitcoin.node=bitcoind
- --bitcoind.rpchost=bitcoind
- --bitcoind.rpcuser=test
- --bitcoind.rpcpass=test
- --bitcoind.zmqpubrawblock=tcp://bitcoind:28332
- --bitcoind.zmqpubrawtx=tcp://bitcoind:28333
ports:
- "10009:10009"
volumes:
- lnd_data:/root/.lnd
volumes:
bitcoin_data:
lnd_data:
CI/CD Integration
# .github/workflows/test.yml
name: Bitcoin Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
services:
bitcoind:
image: ruimarinho/bitcoin-core:latest
options: >-
-regtest
-rpcuser=test
-rpcpassword=test
-rpcallowip=0.0.0.0/0
ports:
- 18443:18443
steps:
- uses: actions/checkout@v3
- name: Wait for Bitcoin Core
run: |
for i in {1..30}; do
if bitcoin-cli -regtest -rpcuser=test -rpcpassword=test getblockchaininfo; then
break
fi
sleep 1
done
- name: Setup test environment
run: |
bitcoin-cli -regtest -rpcuser=test -rpcpassword=test createwallet test
bitcoin-cli -regtest -rpcuser=test -rpcpassword=test -generate 101
- name: Run tests
run: npm test
Network-Specific Code
Selecting Network
RPC Port Configuration
| Network | RPC Port |
|---|---|
| Mainnet | 8332 |
| Testnet | 18332 |
| Signet | 38332 |
| Regtest | 18443 |
Testing Scenarios
Testing RBF (Replace-By-Fee)
# On regtest
# 1. Create transaction with RBF enabled
bitcoin-cli -regtest sendtoaddress <addr> 0.1 "" "" false true # replaceable=true
# 2. Get the txid
TXID=$(bitcoin-cli -regtest listtransactions | jq -r '.[0].txid')
# 3. Bump the fee
bitcoin-cli -regtest bumpfee $TXID
# 4. Mine block to confirm
bitcoin-cli -regtest -generate 1
Testing Time Locks
# Test CLTV (absolute timelock)
# Create transaction locked until block 150
bitcoin-cli -regtest createrawtransaction \
'[{"txid":"...", "vout":0}]' \
'[{"<addr>": 0.1}]' \
150 # locktime
# Won't be valid until block 150
bitcoin-cli -regtest -generate 50 # Advance to block 150+
Testing Mempool Behavior
def test_mempool_eviction(node):
# Fill mempool with low-fee transactions
low_fee_txs = []
for i in range(100):
addr = node.cli('getnewaddress')
txid = node.cli('sendtoaddress', addr, '0.001', '', '', False, False, 1) # 1 sat/vB
low_fee_txs.append(txid)
# Create high-fee transaction
addr = node.cli('getnewaddress')
high_fee_tx = node.cli('sendtoaddress', addr, '0.001', '', '', False, False, 100) # 100 sat/vB
# Verify high-fee tx is in mempool
mempool = node.cli('getrawmempool')
assert high_fee_tx in mempool
# Some low-fee txs may be evicted if mempool is full
Best Practices
Development Workflow
1. Unit Tests → Regtest
- Fast iteration
- Complete control
- Automated testing
2. Integration Tests → Signet
- More realistic environment
- Predictable timing
- Public network testing
3. Final Testing → Testnet
- Closest to mainnet
- Real network conditions
- Edge case discovery
4. Production → Mainnet
- Start with small amounts
- Monitor closely
- Gradual rollout
Common Mistakes
DON'T:
✗ Test with mainnet funds
✗ Assume testnet behavior matches mainnet exactly
✗ Ignore network-specific address formats
✗ Hardcode RPC ports
DO:
✓ Use appropriate network for each test stage
✓ Handle network differences in code
✓ Validate addresses for correct network
✓ Configure networks via environment variables
Summary
Each test network serves a purpose:
- Regtest: Local development and unit testing
- Signet: Predictable integration testing
- Testnet: Real-world simulation before mainnet
Start with regtest for fast iteration, move to signet for integration testing, and use testnet for final validation before deploying to mainnet.
Related Topics
- Testing & Debugging - Testing strategies and techniques
- Getting Started - Development setup guide
- RPC Commands - Bitcoin Core RPC interface
