Skip to content

Commit 550927a

Browse files
committed
rpc: enforce BIP 145 SegWit rules in getblocktemplate
This commit updates the getblocktemplate RPC to enforce the SegWit rules specified in BIP 145. Specifically, it ensures that: - Client requests are rejected with ErrRPCInvalidParameter if SegWit is active but the client does not explicitly support the 'segwit' rule. - The 'rules' array in the response conditionally includes '!segwit' if the generated block template contains transactions with witness data (indicating a witness commitment is required). - The 'rules' array includes 'segwit' (without the '!' prefix) when SegWit is active but the template does not contain any witness transactions. Additionally, integration tests have been added to verify that rule signaling and SegWit activation behave correctly during block template generation.
1 parent 6cfd717 commit 550927a

3 files changed

Lines changed: 173 additions & 7 deletions

File tree

btcjson/chainsvrresults.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,8 @@ type GetBlockTemplateResult struct {
308308
// Block proposal from BIP 0023.
309309
Capabilities []string `json:"capabilities,omitempty"`
310310
RejectReason string `json:"reject-reason,omitempty"`
311+
312+
Rules []string `json:"rules,omitempty"`
311313
}
312314

313315
// GetMempoolEntryResult models the data returned from the getmempoolentry's

integration/rpcserver_test.go

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"time"
1818

1919
"github.com/btcsuite/btcd/blockchain"
20+
"github.com/btcsuite/btcd/btcjson"
2021
"github.com/btcsuite/btcd/chaincfg/v2"
2122
"github.com/btcsuite/btcd/chainhash/v2"
2223
"github.com/btcsuite/btcd/integration/rpctest"
@@ -289,6 +290,138 @@ func testGetNetworkHashPS3(r *rpctest.Harness, t *testing.T) {
289290
}
290291
}
291292

293+
func ensureSegwitActive(r *rpctest.Harness, t *testing.T) {
294+
t.Helper()
295+
296+
for {
297+
info, err := r.Client.GetBlockChainInfo()
298+
if err != nil {
299+
t.Fatalf("unable to get blockchain info: %v", err)
300+
}
301+
302+
if info.Bip9SoftForks == nil || info.Bip9SoftForks["segwit"] == nil {
303+
t.Fatalf("segwit softfork status not found in blockchain info")
304+
}
305+
306+
status := info.Bip9SoftForks["segwit"].Status
307+
if status == "active" {
308+
break
309+
}
310+
311+
if _, err := r.Client.Generate(100); err != nil {
312+
t.Fatalf("unable to generate blocks to activate segwit: %v", err)
313+
}
314+
}
315+
}
316+
317+
func testGetBlockTemplateSegwitActiveNoRule(r *rpctest.Harness, t *testing.T) {
318+
// Guarantee SegWit is fully active before testing
319+
ensureSegwitActive(r, t)
320+
321+
// Call getblocktemplate with empty rules when segwit is active
322+
req := &btcjson.TemplateRequest{
323+
Rules: []string{},
324+
}
325+
326+
_, err := r.Client.GetBlockTemplate(req)
327+
328+
if err == nil {
329+
t.Fatalf("Expected getblocktemplate to fail without 'segwit' rule")
330+
}
331+
332+
// Expect: ErrRPCInvalidParameter with correct message
333+
rpcErr, ok := err.(*btcjson.RPCError)
334+
335+
if !ok {
336+
t.Fatalf("Expected an RPCError, but got: %v", err)
337+
}
338+
339+
if rpcErr.Code != btcjson.ErrRPCInvalidParameter {
340+
t.Fatalf("Expected error code %d, but got: %d", btcjson.ErrRPCInvalidParameter, rpcErr.Code)
341+
}
342+
343+
expectedMessage := "Support for 'segwit' rule requires explicit client support"
344+
345+
if rpcErr.Message != expectedMessage {
346+
t.Fatalf("Expected error message '%s', but got: '%s'", expectedMessage, rpcErr.Message)
347+
}
348+
}
349+
350+
func testGetBlockTemplateSegwitActiveWithRule(r *rpctest.Harness, t *testing.T) {
351+
// Guarantee SegWit is fully active before testing
352+
ensureSegwitActive(r, t)
353+
354+
// Call getblocktemplate with 'segwit' rule when segwit is active
355+
req := &btcjson.TemplateRequest{
356+
Rules: []string{"segwit"},
357+
}
358+
359+
result, err := r.Client.GetBlockTemplate(req)
360+
361+
if err != nil {
362+
t.Fatalf("Expected getblocktemplate to succeed, got error: %v", err)
363+
}
364+
365+
if result == nil {
366+
t.Fatal("Expected non-nil result")
367+
}
368+
369+
hasSegwitRule := false
370+
for _, rule := range result.Rules {
371+
if rule == "segwit" || rule == "!segwit" {
372+
hasSegwitRule = true
373+
break
374+
}
375+
}
376+
377+
if !hasSegwitRule {
378+
t.Fatalf("Expected 'segwit' rule to be present in the response")
379+
}
380+
}
381+
382+
func testGetBlockTemplateResponseRules(r *rpctest.Harness, t *testing.T) {
383+
// Guarantee SegWit is fully active before testing
384+
ensureSegwitActive(r, t)
385+
386+
// Call getblocktemplate with 'segwit' rule when segwit is active
387+
req := &btcjson.TemplateRequest{
388+
Rules: []string{"segwit"},
389+
}
390+
391+
result, err := r.Client.GetBlockTemplate(req)
392+
393+
if err != nil {
394+
t.Fatalf("Expected getblocktemplate to succeed, got error: %v", err)
395+
}
396+
397+
if result == nil {
398+
t.Fatal("Expected non-nil result")
399+
}
400+
401+
// Verify blockTemplateResult includes "!segwit" in Rules when WitnessCommitment is non-nil
402+
// and "segwit" when WitnessCommitment is nil.
403+
hasNotSegwit := false
404+
hasSegwit := false
405+
406+
for _, rule := range result.Rules {
407+
if rule == "!segwit" {
408+
hasNotSegwit = true
409+
} else if rule == "segwit" {
410+
hasSegwit = true
411+
}
412+
}
413+
414+
if result.DefaultWitnessCommitment != "" {
415+
if !hasNotSegwit {
416+
t.Fatalf("Expected Rules to contain '!segwit' because WitnessCommitment is present")
417+
}
418+
} else {
419+
if !hasSegwit {
420+
t.Fatalf("Expected Rules to contain 'segwit' because WitnessCommitment is absent")
421+
}
422+
}
423+
}
424+
292425
var rpcTestCases = []rpctest.HarnessTestCase{
293426
testGetBestBlock,
294427
testGetBlockCount,
@@ -297,6 +430,9 @@ var rpcTestCases = []rpctest.HarnessTestCase{
297430
testGetNetworkHashPS,
298431
testGetNetworkHashPS2,
299432
testGetNetworkHashPS3,
433+
testGetBlockTemplateSegwitActiveNoRule,
434+
testGetBlockTemplateSegwitActiveWithRule,
435+
testGetBlockTemplateResponseRules,
300436
}
301437

302438
var primaryHarness *rpctest.Harness

rpcserver.go

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1688,7 +1688,7 @@ func (state *gbtWorkState) updateBlockTemplate(s *rpcServer, useCoinbaseValue bo
16881688
// and returned to the caller.
16891689
//
16901690
// This function MUST be called with the state locked.
1691-
func (state *gbtWorkState) blockTemplateResult(useCoinbaseValue bool, submitOld *bool) (*btcjson.GetBlockTemplateResult, error) {
1691+
func (state *gbtWorkState) blockTemplateResult(useCoinbaseValue bool, submitOld *bool, segwitActive bool) (*btcjson.GetBlockTemplateResult, error) {
16921692
// Ensure the timestamps are still in valid range for the template.
16931693
// This should really only ever happen if the local clock is changed
16941694
// after the template is generated, but it's important to avoid serving
@@ -1789,6 +1789,10 @@ func (state *gbtWorkState) blockTemplateResult(useCoinbaseValue bool, submitOld
17891789
// data, then include the witness commitment in the GBT result.
17901790
if template.WitnessCommitment != nil {
17911791
reply.DefaultWitnessCommitment = hex.EncodeToString(template.WitnessCommitment)
1792+
reply.Rules = append(reply.Rules, "!segwit")
1793+
1794+
} else if segwitActive {
1795+
reply.Rules = append(reply.Rules, "segwit")
17921796
}
17931797

17941798
if useCoinbaseValue {
@@ -1839,7 +1843,7 @@ func (state *gbtWorkState) blockTemplateResult(useCoinbaseValue bool, submitOld
18391843
// has passed without finding a solution.
18401844
//
18411845
// See https://en.bitcoin.it/wiki/BIP_0022 for more details.
1842-
func handleGetBlockTemplateLongPoll(s *rpcServer, longPollID string, useCoinbaseValue bool, closeChan <-chan struct{}) (interface{}, error) {
1846+
func handleGetBlockTemplateLongPoll(s *rpcServer, longPollID string, useCoinbaseValue bool, closeChan <-chan struct{}, segwitActive bool) (interface{}, error) {
18431847
state := s.gbtWorkState
18441848
state.Lock()
18451849
// The state unlock is intentionally not deferred here since it needs to
@@ -1855,7 +1859,7 @@ func handleGetBlockTemplateLongPoll(s *rpcServer, longPollID string, useCoinbase
18551859
// the caller is invalid.
18561860
prevHash, lastGenerated, err := decodeTemplateID(longPollID)
18571861
if err != nil {
1858-
result, err := state.blockTemplateResult(useCoinbaseValue, nil)
1862+
result, err := state.blockTemplateResult(useCoinbaseValue, nil, segwitActive)
18591863
if err != nil {
18601864
state.Unlock()
18611865
return nil, err
@@ -1877,7 +1881,7 @@ func handleGetBlockTemplateLongPoll(s *rpcServer, longPollID string, useCoinbase
18771881
// already been found and added to the block chain.
18781882
submitOld := prevHash.IsEqual(prevTemplateHash)
18791883
result, err := state.blockTemplateResult(useCoinbaseValue,
1880-
&submitOld)
1884+
&submitOld, segwitActive)
18811885
if err != nil {
18821886
state.Unlock()
18831887
return nil, err
@@ -1917,7 +1921,7 @@ func handleGetBlockTemplateLongPoll(s *rpcServer, longPollID string, useCoinbase
19171921
// block template depending on whether or not a solution has already
19181922
// been found and added to the block chain.
19191923
submitOld := prevHash.IsEqual(&state.template.Block.Header.PrevBlock)
1920-
result, err := state.blockTemplateResult(useCoinbaseValue, &submitOld)
1924+
result, err := state.blockTemplateResult(useCoinbaseValue, &submitOld, segwitActive)
19211925
if err != nil {
19221926
return nil, err
19231927
}
@@ -1986,12 +1990,36 @@ func handleGetBlockTemplateRequest(s *rpcServer, request *btcjson.TemplateReques
19861990
}
19871991
}
19881992

1993+
segwitState, err := s.cfg.Chain.ThresholdState(chaincfg.DeploymentSegwit)
1994+
if err != nil {
1995+
return nil, err
1996+
}
1997+
1998+
segwitActive := segwitState == blockchain.ThresholdActive
1999+
hasSegwitRule := false
2000+
if request != nil {
2001+
for _, rule := range request.Rules {
2002+
if rule == "segwit" {
2003+
hasSegwitRule = true
2004+
break
2005+
}
2006+
}
2007+
}
2008+
2009+
if segwitActive && !hasSegwitRule {
2010+
return nil, &btcjson.RPCError{
2011+
Code: btcjson.ErrRPCInvalidParameter,
2012+
Message: "Support for 'segwit' rule requires explicit " +
2013+
"client support",
2014+
}
2015+
}
2016+
19892017
// When a long poll ID was provided, this is a long poll request by the
19902018
// client to be notified when block template referenced by the ID should
19912019
// be replaced with a new one.
19922020
if request != nil && request.LongPollID != "" {
19932021
return handleGetBlockTemplateLongPoll(s, request.LongPollID,
1994-
useCoinbaseValue, closeChan)
2022+
useCoinbaseValue, closeChan, segwitActive)
19952023
}
19962024

19972025
// Protect concurrent access when updating block templates.
@@ -2008,7 +2036,7 @@ func handleGetBlockTemplateRequest(s *rpcServer, request *btcjson.TemplateReques
20082036
if err := state.updateBlockTemplate(s, useCoinbaseValue); err != nil {
20092037
return nil, err
20102038
}
2011-
return state.blockTemplateResult(useCoinbaseValue, nil)
2039+
return state.blockTemplateResult(useCoinbaseValue, nil, segwitActive)
20122040
}
20132041

20142042
// chainErrToGBTErrString converts an error returned from btcchain to a string

0 commit comments

Comments
 (0)