Skip to content

[bug]: rpc client freezes if .GetBlockTemplate is called after a block comes in and the websocket interface is used #2465

@0xf0xx0

Description

@0xf0xx0

Background

when .GetBlockTemplate is called right after a block rolls in, and the websocket interface is in use, btcd fails and GetBlockTemplate hangs. trying to then stop the client with .Shutdown() also hangs.

Your environment

  • version of btcd: 0.25.0
  • which operating system: EndeavourOS
  • any other relevant environment details
    imported deps:
github.com/btcsuite/btcd v0.25.0
github.com/btcsuite/btcd/btcutil v1.1.7-0.20251106010755-9ff0780da683
github.com/btcsuite/btcd/btcec/v2 v2.3.6 // indirect
github.com/btcsuite/btclog v1.0.0 // indirect
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd // indirect
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 // indirect

Steps to reproduce

  1. Set up rpcclient using the websocket interface
  2. trigger a gbt after blocks come in
  3. wait a couple hours for it to freeze
  4. try to SIGINT
  5. watch it not stop

I sometimes (sometimes!!!! >:C) get an error from btcd too when it occurs:

2025-11-14 04:01:43.355 [DBG] RPCS: Received command <getblocktemplate> from [::1]:44984  # erroring gbt
2025-11-14 04:01:43.355 [DBG] MINR: Considering 2987 transactions for inclusion to new block
2025-11-14 04:01:43.853 [ERR] RPCS: Failed to create new block template: previous block must be the current chain tip 00000000000000000000a6c2839f8c2bea4f7b31c63f4bce11303007f85b3653, instead got 000000000000000000002a22ca81d279bd95d772b7c8fa3e5ed7a0ba303b0694
2025-11-14 04:01:44.356 [DBG] RPCS: Received command <getblocktemplate> from [::1]:44984  # retry loop, iter 1
2025-11-14 04:01:44.356 [DBG] MINR: Considering 573 transactions for inclusion to new block
2025-11-14 04:01:44.657 [DBG] MINR: Created new block template (501 transactions, 361584 in fees, 2031 signature operations cost, 3875333 weight, target difficulty 00000000000000000001d9360000000000000000000000000000000000000000)
2025-11-14 04:01:44.657 [DBG] RPCS: Generated block template (timestamp 2025-11-14 04:01:44 -0500 EST, target 00000000000000000001d9360000000000000000000000000000000000000000, merkle root 16fff92883e2c19c15062f2f727352734ca537ed37030fd7127b3db7688e5729)
# this is where gbt hangs in the code snippet
code sample:
package main

import (
	"log"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/btcsuite/btcd/btcjson"
	"github.com/btcsuite/btcd/btcutil"
	"github.com/btcsuite/btcd/rpcclient"
	"github.com/btcsuite/btcd/wire"
)

func main() {
	sigs := make(chan os.Signal, 1)
	signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)

	triggerGBT := make(chan struct{})
	backendConnConf := &rpcclient.ConnConfig{
		Host:         "localhost:8334",
		DisableTLS:   true,
		HTTPPostMode: false,
		Endpoint:     "ws",
		User:         "btcd",
		Pass:         "btcd",
	}
	backend, err := rpcclient.New(backendConnConf, &rpcclient.NotificationHandlers{
		OnFilteredBlockConnected: func(height int32, header *wire.BlockHeader, _ []*btcutil.Tx) {
			log.Printf("new block %d\n%s\n", height, header.BlockHash())
			/// GBT fail trigger
			triggerGBT <- struct{}{}
		},
		OnFilteredBlockDisconnected: func(_ int32, _ *wire.BlockHeader) {},
	})
	if err != nil {
		log.Panicf("failed to connect to backend: %s", err)
	}
	if err := backend.NotifyBlocks(); err != nil {
		log.Panicf("failed to subscribe to block notifications: %s", err)
	}
	go func() {
		log.Print("waiting for blocks...")
		for {
			template, err := backend.GetBlockTemplate(&btcjson.TemplateRequest{
				Rules:        []string{"segwit"}, /// required by gbt
				Capabilities: []string{"proposal", "coinbasevalue", "longpoll"},
				Mode:         "template",
			})
			if err != nil {
				log.Printf("error fetching template: %s\n", err)
				time.Sleep(time.Millisecond * 500)
				continue
			}
			log.Printf("gbt success, %d txns\n", len(template.Transactions))
			<-triggerGBT
		}
	}()
	<-sigs
	log.Println("shutting down")
	backend.Shutdown() /// Doesn't progress past here
	log.Println("waiting for shutdown")
	backend.WaitForShutdown()
}

running this consistently triggers the error after a couple of hours.

Expected behaviour

i expected the client to not freeze.

Actual behaviour

the client freezes and needs to be SIGKILLed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions