B++ Logo

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

FeatureMainnetTestnetSignetRegtest
Real ValueYesNoNoNo
Block Time~10 minVariable~10 minInstant
DifficultyDynamicDynamicFixedMinimal
PublicYesYesYesLocal
ControlledNoNoYesYes
Best ForProductionIntegrationTestingUnit 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 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

NetworkRPC Port
Mainnet8332
Testnet18332
Signet38332
Regtest18443

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.