This commit is contained in:
Mubarak Abdullateef 2026-08-03 18:57:28 +00:00 committed by GitHub
commit 028343d3bf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 192 additions and 8 deletions

View file

@ -308,6 +308,8 @@ type GetBlockTemplateResult struct {
// Block proposal from BIP 0023.
Capabilities []string `json:"capabilities,omitempty"`
RejectReason string `json:"reject-reason,omitempty"`
Rules []string `json:"rules,omitempty"`
}
// GetMempoolEntryResult models the data returned from the getmempoolentry's

View file

@ -17,6 +17,7 @@ import (
"time"
"github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/btcjson"
"github.com/btcsuite/btcd/chaincfg/v2"
"github.com/btcsuite/btcd/chainhash/v2"
"github.com/btcsuite/btcd/integration/rpctest"
@ -289,6 +290,151 @@ func testGetNetworkHashPS3(r *rpctest.Harness, t *testing.T) {
}
}
func ensureSegwitActive(r *rpctest.Harness, t *testing.T) {
t.Helper()
for {
info, err := r.Client.GetBlockChainInfo()
if err != nil {
t.Fatalf("unable to get blockchain info: %v", err)
}
if info.Bip9SoftForks == nil ||
info.Bip9SoftForks["segwit"] == nil {
t.Fatalf("segwit softfork status not found in" +
" blockchain info")
}
status := info.Bip9SoftForks["segwit"].Status
if status == "active" {
break
}
if _, err := r.Client.Generate(100); err != nil {
t.Fatalf("unable to generate blocks to activate "+
"segwit: %v", err)
}
}
}
func testGetBlockTemplateSegwitActiveNoRule(r *rpctest.Harness, t *testing.T) {
// Guarantee SegWit is fully active before testing
ensureSegwitActive(r, t)
// Call getblocktemplate with empty rules when segwit is active
req := &btcjson.TemplateRequest{
Rules: []string{},
}
_, err := r.Client.GetBlockTemplate(req)
if err == nil {
t.Fatalf("Expected getblocktemplate to fail without" +
" 'segwit' rule")
}
// Expect: ErrRPCInvalidParameter with correct message
rpcErr, ok := err.(*btcjson.RPCError)
if !ok {
t.Fatalf("Expected an RPCError, but got: %v", err)
}
if rpcErr.Code != btcjson.ErrRPCInvalidParameter {
t.Fatalf("Expected error code %d, but got: %d",
btcjson.ErrRPCInvalidParameter, rpcErr.Code)
}
expectedMessage := "Support for 'segwit' rule requires explicit " +
"client support"
if rpcErr.Message != expectedMessage {
t.Fatalf("Expected error message '%s', but got: '%s'",
expectedMessage, rpcErr.Message)
}
}
func testGetBlockTemplateSegwitActiveWithRule(
r *rpctest.Harness, t *testing.T) {
// Guarantee SegWit is fully active before testing
ensureSegwitActive(r, t)
// Call getblocktemplate with 'segwit' rule when segwit is active
req := &btcjson.TemplateRequest{
Rules: []string{"segwit"},
}
result, err := r.Client.GetBlockTemplate(req)
if err != nil {
t.Fatalf("Expected getblocktemplate to succeed, got "+
"error: %v", err)
}
if result == nil {
t.Fatal("Expected non-nil result")
}
hasSegwitRule := false
for _, rule := range result.Rules {
if rule == "segwit" || rule == "!segwit" {
hasSegwitRule = true
break
}
}
if !hasSegwitRule {
t.Fatalf("Expected 'segwit' rule to be present in the response")
}
}
func testGetBlockTemplateResponseRules(r *rpctest.Harness, t *testing.T) {
// Guarantee SegWit is fully active before testing
ensureSegwitActive(r, t)
// Call getblocktemplate with 'segwit' rule when segwit is active
req := &btcjson.TemplateRequest{
Rules: []string{"segwit"},
}
result, err := r.Client.GetBlockTemplate(req)
if err != nil {
t.Fatalf("Expected getblocktemplate to succeed, got "+
"error: %v", err)
}
if result == nil {
t.Fatal("Expected non-nil result")
}
// Verify blockTemplateResult includes "!segwit" in Rules when
// WitnessCommitment is non-nil and "segwit" when WitnessCommitment
// is nil.
hasNotSegwit := false
hasSegwit := false
for _, rule := range result.Rules {
if rule == "!segwit" {
hasNotSegwit = true
} else if rule == "segwit" {
hasSegwit = true
}
}
if result.DefaultWitnessCommitment != "" {
if !hasNotSegwit {
t.Fatalf("Expected Rules to contain '!segwit' because" +
" WitnessCommitment is present")
}
} else {
if !hasSegwit {
t.Fatalf("Expected Rules to contain 'segwit' because " +
"WitnessCommitment is absent")
}
}
}
var rpcTestCases = []rpctest.HarnessTestCase{
testGetBestBlock,
testGetBlockCount,
@ -297,6 +443,9 @@ var rpcTestCases = []rpctest.HarnessTestCase{
testGetNetworkHashPS,
testGetNetworkHashPS2,
testGetNetworkHashPS3,
testGetBlockTemplateSegwitActiveNoRule,
testGetBlockTemplateSegwitActiveWithRule,
testGetBlockTemplateResponseRules,
}
var primaryHarness *rpctest.Harness

View file

@ -20,6 +20,7 @@ import (
"net"
"net/http"
"os"
"slices"
"strconv"
"strings"
"sync"
@ -1704,7 +1705,9 @@ func (state *gbtWorkState) updateBlockTemplate(s *rpcServer, useCoinbaseValue bo
// and returned to the caller.
//
// This function MUST be called with the state locked.
func (state *gbtWorkState) blockTemplateResult(useCoinbaseValue bool, submitOld *bool) (*btcjson.GetBlockTemplateResult, error) {
func (state *gbtWorkState) blockTemplateResult(
useCoinbaseValue, segwitActive bool,
submitOld *bool) (*btcjson.GetBlockTemplateResult, error) {
// Ensure the timestamps are still in valid range for the template.
// This should really only ever happen if the local clock is changed
// after the template is generated, but it's important to avoid serving
@ -1805,6 +1808,10 @@ func (state *gbtWorkState) blockTemplateResult(useCoinbaseValue bool, submitOld
// data, then include the witness commitment in the GBT result.
if template.WitnessCommitment != nil {
reply.DefaultWitnessCommitment = hex.EncodeToString(template.WitnessCommitment)
reply.Rules = append(reply.Rules, "!segwit")
} else if segwitActive {
reply.Rules = append(reply.Rules, "segwit")
}
if useCoinbaseValue {
@ -1855,7 +1862,9 @@ func (state *gbtWorkState) blockTemplateResult(useCoinbaseValue bool, submitOld
// has passed without finding a solution.
//
// See https://en.bitcoin.it/wiki/BIP_0022 for more details.
func handleGetBlockTemplateLongPoll(s *rpcServer, longPollID string, useCoinbaseValue bool, closeChan <-chan struct{}) (interface{}, error) {
func handleGetBlockTemplateLongPoll(
s *rpcServer, longPollID string, closeChan <-chan struct{},
useCoinbaseValue, segwitActive bool) (interface{}, error) {
state := s.gbtWorkState
state.Lock()
// The state unlock is intentionally not deferred here since it needs to
@ -1871,7 +1880,9 @@ func handleGetBlockTemplateLongPoll(s *rpcServer, longPollID string, useCoinbase
// the caller is invalid.
prevHash, lastGenerated, err := decodeTemplateID(longPollID)
if err != nil {
result, err := state.blockTemplateResult(useCoinbaseValue, nil)
result, err := state.blockTemplateResult(
useCoinbaseValue, segwitActive, nil,
)
if err != nil {
state.Unlock()
return nil, err
@ -1892,8 +1903,8 @@ func handleGetBlockTemplateLongPoll(s *rpcServer, longPollID string, useCoinbase
// old block template depending on whether or not a solution has
// already been found and added to the block chain.
submitOld := prevHash.IsEqual(prevTemplateHash)
result, err := state.blockTemplateResult(useCoinbaseValue,
&submitOld)
result, err := state.blockTemplateResult(
useCoinbaseValue, segwitActive, &submitOld)
if err != nil {
state.Unlock()
return nil, err
@ -1933,7 +1944,9 @@ func handleGetBlockTemplateLongPoll(s *rpcServer, longPollID string, useCoinbase
// block template depending on whether or not a solution has already
// been found and added to the block chain.
submitOld := prevHash.IsEqual(&state.template.Block.Header.PrevBlock)
result, err := state.blockTemplateResult(useCoinbaseValue, &submitOld)
result, err := state.blockTemplateResult(
useCoinbaseValue, segwitActive, &submitOld,
)
if err != nil {
return nil, err
}
@ -2002,12 +2015,31 @@ func handleGetBlockTemplateRequest(s *rpcServer, request *btcjson.TemplateReques
}
}
segwitState, err := s.cfg.Chain.ThresholdState(
chaincfg.DeploymentSegwit,
)
if err != nil {
return nil, err
}
segwitActive := segwitState == blockchain.ThresholdActive
hasSegwitRule := request != nil && slices.Contains(
request.Rules, "segwit",
)
if segwitActive && !hasSegwitRule {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCInvalidParameter,
Message: "Support for 'segwit' rule requires explicit" +
" client support",
}
}
// When a long poll ID was provided, this is a long poll request by the
// client to be notified when block template referenced by the ID should
// be replaced with a new one.
if request != nil && request.LongPollID != "" {
return handleGetBlockTemplateLongPoll(s, request.LongPollID,
useCoinbaseValue, closeChan)
closeChan, useCoinbaseValue, segwitActive)
}
// Protect concurrent access when updating block templates.
@ -2024,7 +2056,7 @@ func handleGetBlockTemplateRequest(s *rpcServer, request *btcjson.TemplateReques
if err := state.updateBlockTemplate(s, useCoinbaseValue); err != nil {
return nil, err
}
return state.blockTemplateResult(useCoinbaseValue, nil)
return state.blockTemplateResult(useCoinbaseValue, segwitActive, nil)
}
// chainErrToGBTErrString converts an error returned from btcchain to a string

View file

@ -340,6 +340,7 @@ var helpDescsEnUS = map[string]string{
"getblocktemplateresult-reject-reason": "Reason the proposal was invalid as-is (only applies to proposal responses)",
"getblocktemplateresult-default_witness_commitment": "The witness commitment itself. Will be populated if the block has witness data",
"getblocktemplateresult-weightlimit": "The current limit on the max allowed weight of a block",
"getblocktemplateresult-rules": "List of rules the server requires the client to understand and support",
// GetBlockTemplateCmd help.
"getblocktemplate--synopsis": "Returns a JSON object with information necessary to construct a block to mine or accepts a proposal to validate.\n" +