Skip to content

btcd: fix nonzero exit code on sigint when indexing cfindex#2557

Open
allocz wants to merge 1 commit into
btcsuite:masterfrom
allocz:fix_nonzero_exit_code_on_sigint_when_indexing_cfindex
Open

btcd: fix nonzero exit code on sigint when indexing cfindex#2557
allocz wants to merge 1 commit into
btcsuite:masterfrom
allocz:fix_nonzero_exit_code_on_sigint_when_indexing_cfindex

Conversation

@allocz

@allocz allocz commented Jun 22, 2026

Copy link
Copy Markdown

Change Description

Fixes #2530 by returning nil instead of error when newServer returns error because an interrupt was received.

Steps to Test

  1. Sync btcd up to some height, then stop the node;
  2. Start btcd with --dropcfindex, btcd will stop after dropping the index;
  3. Start btcd again and send ctrl+c after seeing a log containing [INF] INDX: Catching up indexes from height;
  4. The status code should be zero with the patch, and not zero without the patch.

Pull Request Checklist

Testing

  • Your PR passes all CI checks.
  • Tests covering the positive and negative (error paths) are included.
  • Bug fixes contain tests triggering the bug to prevent regressions.

Code Style and Documentation

📝 Please see our Contribution Guidelines for further guidance.

@allocz allocz force-pushed the fix_nonzero_exit_code_on_sigint_when_indexing_cfindex branch from 738cf8b to 9fe11c8 Compare June 22, 2026 18:51
@allocz allocz changed the title Fix nonzero exit code on sigint when indexing cfindex btcd: fix nonzero exit code on sigint when indexing cfindex Jun 22, 2026
Comment thread btcd.go Outdated
Comment thread btcd.go
@allocz allocz force-pushed the fix_nonzero_exit_code_on_sigint_when_indexing_cfindex branch from 9fe11c8 to cab7fd2 Compare June 25, 2026 18:21
Comment thread btcd.go Outdated
@allocz allocz force-pushed the fix_nonzero_exit_code_on_sigint_when_indexing_cfindex branch from cab7fd2 to e24a370 Compare June 29, 2026 12:20
@saubyk saubyk requested a review from kcalvinalvin June 30, 2026 00:26
@allocz allocz force-pushed the fix_nonzero_exit_code_on_sigint_when_indexing_cfindex branch from e24a370 to 2706da3 Compare July 8, 2026 19:54
@allocz

allocz commented Jul 10, 2026

Copy link
Copy Markdown
Author

The following diff which depends on #2571 reproduces the bug.

diff --git a/blockchain/indexers/manager.go b/blockchain/indexers/manager.go
index 35994688..55900dd6 100644
--- a/blockchain/indexers/manager.go
+++ b/blockchain/indexers/manager.go
@@ -12,6 +12,7 @@ import (
        "github.com/btcsuite/btcd/btcutil/v2"
        "github.com/btcsuite/btcd/chainhash/v2"
        "github.com/btcsuite/btcd/database"
+       "github.com/btcsuite/btcd/debugstream"
        "github.com/btcsuite/btcd/wire/v2"
 )

@@ -400,6 +401,9 @@ func (m *Manager) Init(chain *blockchain.BlockChain, interrupt <-chan struct{})
        // At this point, one or more indexes are behind the current best chain
        // tip and need to be caught up, so log the details and loop through
        // each block that needs to be indexed.
+       debugstream.S.Broadcast(debugstream.Event{
+               Code: debugstream.DECatchingUpIndexes,
+       })
        log.Infof("Catching up indexes from height %d to %d", lowestHeight,
                bestHeight)
        for height := lowestHeight + 1; height <= bestHeight; height++ {
diff --git a/config.go b/config.go
index 43cb4d64..e3c4c847 100644
--- a/config.go
+++ b/config.go
@@ -148,6 +148,7 @@ type config struct {
        OnionProxy           string        `long:"onion" description:"Connect to tor hidden services via SOCKS5 proxy (eg. 127.0.0.1:9050)"`
        OnionProxyPass       string        `long:"onionpass" default-mask:"-" description:"Password for onion proxy server"`
        OnionProxyUser       string        `long:"onionuser" description:"Username for onion proxy server"`
+       PowNoRetarget        bool          `long:"pownoretarget" hidden:"true" description:"Disable difficulty retarget for simulation network"`
        Profile              string        `long:"profile" description:"Enable HTTP profiling on given port -- NOTE port must be between 1024 and 65536"`
        Proxy                string        `long:"proxy" description:"Connect via SOCKS5 proxy (eg. 127.0.0.1:9050)"`
        ProxyPass            string        `long:"proxypass" default-mask:"-" description:"Password for proxy server"`
@@ -566,6 +567,9 @@ func loadConfig() (*config, []string, error) {
                // Also disable dns seeding on the simulation test network.
                activeNetParams = &simNetParams
                cfg.DisableDNSSeed = true
+               if cfg.PowNoRetarget {
+                       activeNetParams.PoWNoRetargeting = true
+               }
        }
        if cfg.SigNet {
                numNets++
diff --git a/debugstream/codes.go b/debugstream/codes.go
index c808063e..a6f83f68 100644
--- a/debugstream/codes.go
+++ b/debugstream/codes.go
@@ -5,4 +5,5 @@ package debugstream
 const (
        DEStart = iota + 1
        DEShutdown
+       DECatchingUpIndexes
 )
diff --git a/integration/cfindex_test.go b/integration/cfindex_test.go
new file mode 100644
index 00000000..7d337e6d
--- /dev/null
+++ b/integration/cfindex_test.go
@@ -0,0 +1,80 @@
+//go:build rpctest
+
+package integration_test
+
+import (
+       "testing"
+       "time"
+
+       "github.com/btcsuite/btcd/debugstream"
+       "github.com/btcsuite/btcd/integration/rpctest"
+       "github.com/stretchr/testify/require"
+)
+
+// TestSuccessExitOnSigintWhileDoingCFIndex was introduced as a regression test
+// for a bug that consists in btcd exiting with non zero code when a exit signal
+// is sent while cfindex is being built.
+func TestSuccessExitOnSigintWhileDoingCFIndex(t *testing.T) {
+       // On DECatchingUpIndexes event, send node teardown and assert that the
+       // node exited with no error.
+       const (
+               sStart int = iota
+               sCatchingUpIndexes
+       )
+       state := -1
+       okCh := make(chan struct{})
+       dHandler := func(e debugstream.Event) {
+               expected := state == sStart &&
+                       e.Code == debugstream.DECatchingUpIndexes
+               if !expected {
+                       return
+               }
+
+               state = sCatchingUpIndexes
+               close(okCh)
+       }
+
+       // We need to send the exit signal between the start of the indexing
+       // and the end of it, with too few blocks we can't trigger the bug, with
+       // the test hardware, the non zero exit signal could be triggered with
+       // 2000 blocks, so let's use 20000 to be able to reproduce this
+       // regression test even in machines that handle the indexing up to 10x
+       // faster.
+       const nBlocks = 20000
+
+       // Mine some blocks.
+       h, err := rpctest.New(rpctest.Opts{DebugHandler: dHandler})
+       require.NoError(t, err)
+       require.NoError(t, h.SetUp(rpctest.SOpts{
+               // Difficulty retarget is disabled because we want nBlocks to
+               // be generated quickly, without it the test would take too
+               // long to complete.
+               Args: []string{"--pownoretarget"},
+               UTXOCount: (nBlocks-100),
+       }))
+       require.NoError(t, h.TearDown(rpctest.TOpts{NoNodeCleanup: true}))
+
+       // Start with dropcfindex and wait for shutdown.
+       require.NoError(t, h.SetUp(rpctest.SOpts{
+               NoRPCAndWallet: true,
+               Args: []string{"--pownoretarget", "--dropcfindex"},
+       }))
+       require.NoError(t, h.TearDown(rpctest.TOpts{
+               NoNodeCleanup: true,
+               NoShutdownSignal: true,
+       }))
+
+       state = sStart
+       require.NoError(t, h.SetUp(rpctest.SOpts{
+               Args: []string{"--pownoretarget"},
+               NoRPCAndWallet: true,
+       }))
+       select {
+       case <-time.After(time.Second * 10):
+               t.Fatal("timeout")
+       case <-okCh:
+       }
+       // If the interrupt error is not handled, the node will exit with non
+       // zero status code, and we will catch this here.
+       require.NoError(t, h.TearDown())
+}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[bug]: utreexod exits with status=1/FAILURE when indexing cfilters

2 participants