mirror of
https://github.com/btcsuite/btcd.git
synced 2026-08-16 13:00:29 +02:00
Merge 48e403e8ea into 05585e037b
This commit is contained in:
commit
028343d3bf
4 changed files with 192 additions and 8 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
48
rpcserver.go
48
rpcserver.go
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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" +
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue