Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# go-regtest

A lightweight Go library for managing Bitcoin Core regtest environments.
A lightweight Go library for managing Bitcoin Core or Bitcoin Inquisition regtest environments.

[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![Go Version](https://img.shields.io/badge/Go-1.23%2B-blue.svg)](https://golang.org)
Expand Down Expand Up @@ -167,15 +167,17 @@ rt, _ := regtest.New(&regtest.Config{
rt.Start(); defer rt.Stop()
status, _ := rt.DeploymentStatus("testdummy") // SoftForkDefined / Started / ...

// Mine through retarget windows until ACTIVE.
// Mine through retarget windows until ACTIVE. The `MineUntilActive` /
// `MineUntilActiveBIP` helpers wrap this loop; the snippet inlines it
// here only to show the underlying state machine.
miner, _ := rt.GenerateBech32("miner")
for status != regtest.SoftForkActive {
rt.Warp(144, miner)
status, _ = rt.DeploymentStatus("testdummy")
}
```

For a fully-narrated walkthrough, see [`TestExampleActivateTestdummy`](examples_test.go) — the same template applies to real future soft-forks (APO/eltoo, CTV, CSFS) once you point `bitcoind` in `$PATH` at a binary that knows the deployment.
For a fully-narrated walkthrough, see [`TestExampleActivateTestdummy`](examples_test.go) — the same template applies to real future soft-forks (APO/eltoo, CTV, CSFS) once you point `bitcoind` in `$PATH` at a binary that knows the deployment. For Inquisition-tracked BIPs, prefer the typed `rt.MineUntilActiveBIP(regtest.BIP119, addr, maxBlocks)` over the string-keyed `MineUntilActive` so deployment-name typos surface at compile time.

#### Skip-when-missing pattern

Expand All @@ -198,7 +200,7 @@ func TestMyCTVThing(t *testing.T) {
}
```

`rt.ListDeployments()` returns the merged registry-and-live view (`BIPID`, `BIPNumber`, `Name`, `DocURL`, `Status`, `Type`, `Active`, `Height`) keyed by deployment string, useful for diagnostics. See [`TestExampleActivateBIP119`](examples_inquisition_test.go) for the full template.
`rt.ListDeployments()` returns a `[]EnrichedDeployment` (joined registry + live view: `BIPID`, `BIPNumber`, `Name`, `DocURL`, `Status`, `Type`, `Active`, `Height`) sorted alphabetically by `Deployment`, useful for diagnostics. See [`TestExampleActivateBIP119`](examples_inquisition_test.go) for the full template.

### Multi-node and reorg testing

Expand Down
46 changes: 42 additions & 4 deletions doc.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
/*
Package regtest provides a lightweight Go library for managing Bitcoin Core regtest environments.
Package regtest provides a lightweight Go library for managing Bitcoin Core
or Bitcoin Inquisition regtest environments.

Regtest mode creates a private blockchain for testing and development. This package simplifies
starting, managing, and interacting with regtest nodes programmatically.
starting, managing, and interacting with regtest nodes programmatically. The same Config works
against stock Bitcoin Core and against Bitcoin Inquisition (the experimental Core fork that
activates upcoming soft forks: BIP54, BIP118 ANYPREVOUT, BIP119 OP_CHECKTEMPLATEVERIFY,
BIP347 OP_CAT, BIP348 OP_CHECKSIGFROMSTACK, BIP349 OP_INTERNALKEY).

Quick Start

Expand Down Expand Up @@ -36,8 +40,11 @@ Default settings:
- RPC user: user
- RPC pass: pass
- Data directory: ./bitcoind_regtest
- Binary: PATH auto-detect — bitcoind-inquisition first, then bitcoind

Customize via Config struct when creating instances.
Customize via Config struct when creating instances. Set Config.BinaryPath to point at a
non-default bitcoind build (absolute path, relative path, or bare name resolved via PATH).
The bitcoin-cli companion is derived from the same directory, falling back to PATH.

# Examples

Expand Down Expand Up @@ -82,6 +89,28 @@ Direct RPC Access:
info, _ := client.GetBlockChainInfo()
mempool, _ := client.GetRawMempool()

# Soft-fork Testing

VBParams configure named BIP9 deployments via -vbparams. DeploymentStatus and
GetDeploymentInfo expose the current state machine; MineUntilActive (string-keyed) and
MineUntilActiveBIP (typed BIPID) drive a deployment to SoftForkActive over retarget windows.

The curated registry maps typed BIPID constants to deployment names, BIP numbers, and doc
URLs:

- BIPTestdummy, BIPTaproot — present on both Core and Inquisition
- BIP54, BIP118, BIP119, BIP347, BIP348, BIP349 — Inquisition-only

ListDeployments returns the merged registry-and-live view; SupportsBIP is the canonical
skip-when-missing primitive for tests that need an Inquisition-only deployment:

if ok, _ := rt.SupportsBIP(regtest.BIP119); !ok {
t.Skip("requires bitcoind-inquisition")
}

Variant reports VariantCore or VariantInquisition (parsed from getnetworkinfo.subversion)
once Start has succeeded; the result is cached so repeat calls are free.

# Thread Safety

All Regtest methods are thread-safe. Multiple goroutines can safely call Start(), Stop(),
Expand All @@ -90,19 +119,28 @@ IsRunning(), and make RPC calls concurrently. Always use defer rt.Stop() for cle
# Error Handling

Check errors from all methods. Common errors:
- bitcoind not found in PATH
- bitcoind not found in PATH (tried bitcoind-inquisition, bitcoind)
- Port already in use
- RPC connection failures
- Invalid addresses or parameters
- Insufficient funds

Sentinels are errors.Is-compatible:
- errNotConnected — RPC method called before Start
- ErrUnknownDeployment — deployment name not in getdeploymentinfo
- ErrUnknownBIP — BIPID not in the curated registry

# Prerequisites

Install Bitcoin Core:
- macOS: brew install bitcoin
- Ubuntu/Debian: sudo apt-get install bitcoind
- Arch: sudo pacman -S bitcoin-core

For testing upcoming soft forks, build Bitcoin Inquisition from source — see the README
for the cmake recipe. The built bitcoind can be picked up via Config.BinaryPath, or by
symlinking it as bitcoind-inquisition on PATH so the auto-detect chain finds it.

# Port Considerations

When running multiple instances, use widely spaced ports (e.g., 19000, 19100) because Bitcoin
Expand Down
59 changes: 54 additions & 5 deletions rpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,53 @@ import (
"github.com/btcsuite/btcd/rpcclient"
)

// Client returns the RPC client for the Regtest instance.
// For advanced users that want to use the RPC client directly.
// Client returns the underlying btcsuite/rpcclient connection for callers
// that need an RPC the typed wrappers in this package don't cover. The
// returned client is shared with the wrappers; callers must not Shutdown it.
//
// Returns:
// - *rpcclient.Client: The RPC client instance, or nil if not connected
// - *rpcclient.Client: the live client, or nil before Start has succeeded
// (or after Stop). Prefer the typed wrappers on Regtest where available.
//
// Example:
//
// client := rt.Client()
// if client == nil {
// return errors.New("Start must be called first")
// }
// info, _ := client.GetBlockChainInfo()
func (r *Regtest) Client() *rpcclient.Client {
r.clientMu.RLock()
defer r.clientMu.RUnlock()
return r.client
}

// GetBlockCount returns the current block count.
// GetBlockCount returns the chain tip height. Convenience wrapper around
// GetBlockCountContext using context.Background().
//
// Returns:
// - int64: current block height (0 on a fresh regtest node before any
// blocks have been mined).
// - error: errNotConnected before Start; otherwise the wrapped RPC error.
//
// Example:
//
// h, err := rt.GetBlockCount()
// if err != nil { return err }
// fmt.Printf("tip at height %d\n", h)
func (r *Regtest) GetBlockCount() (int64, error) {
return r.GetBlockCountContext(context.Background())
}

// GetBlockCountContext is the context-aware variant of GetBlockCount.
//
// Parameters:
// - ctx: cancellation / timeout. A pre-cancelled context returns ctx.Err().
//
// Returns:
// - int64: current block height.
// - error: errNotConnected before Start; ctx.Err() on cancellation;
// otherwise the wrapped RPC error.
func (r *Regtest) GetBlockCountContext(ctx context.Context) (int64, error) {
client, err := r.lockedClient()
if err != nil {
Expand All @@ -35,12 +65,31 @@ func (r *Regtest) GetBlockCountContext(ctx context.Context) (int64, error) {
})
}

// HealthCheck performs a health check by getting the block count.
// HealthCheck performs a minimal RPC round-trip (getblockcount) to confirm
// the node is reachable and responsive. Convenience wrapper around
// HealthCheckContext using context.Background().
//
// Returns:
// - error: errNotConnected before Start; otherwise the wrapped RPC error
// from getblockcount.
//
// Example:
//
// if err := rt.HealthCheck(); err != nil {
// t.Fatalf("node not healthy: %v", err)
// }
func (r *Regtest) HealthCheck() error {
return r.HealthCheckContext(context.Background())
}

// HealthCheckContext is the context-aware variant of HealthCheck.
//
// Parameters:
// - ctx: cancellation / timeout. A pre-cancelled context returns ctx.Err().
//
// Returns:
// - error: errNotConnected before Start; ctx.Err() on cancellation;
// otherwise the wrapped RPC error.
func (r *Regtest) HealthCheckContext(ctx context.Context) error {
if _, err := r.GetBlockCountContext(ctx); err != nil {
return fmt.Errorf("failed to get block count (health check): %w", err)
Expand Down
Loading