diff --git a/btcjson/chainsvrcmds.go b/btcjson/chainsvrcmds.go index a5e56471..406357bd 100644 --- a/btcjson/chainsvrcmds.go +++ b/btcjson/chainsvrcmds.go @@ -638,6 +638,7 @@ func NewSearchRawTransactionsCmd(address string, verbose, skip, count *int, vinE type SendRawTransactionCmd struct { HexTx string AllowHighFees *bool `jsonrpcdefault:"false"` + MaxFeeRate *int32 } // NewSendRawTransactionCmd returns a new instance which can be used to issue a @@ -652,6 +653,17 @@ func NewSendRawTransactionCmd(hexTx string, allowHighFees *bool) *SendRawTransac } } +// NewSendRawTransactionCmd returns a new instance which can be used to issue a +// sendrawtransaction JSON-RPC command to a bitcoind node. +// +// A 0 maxFeeRate indicates that a maximum fee rate won't be enforced. +func NewBitcoindSendRawTransactionCmd(hexTx string, maxFeeRate int32) *SendRawTransactionCmd { + return &SendRawTransactionCmd{ + HexTx: hexTx, + MaxFeeRate: &maxFeeRate, + } +} + // SetGenerateCmd defines the setgenerate JSON-RPC command. type SetGenerateCmd struct { Generate bool diff --git a/btcjson/chainsvrresults.go b/btcjson/chainsvrresults.go index cc9c0855..7e6c7107 100644 --- a/btcjson/chainsvrresults.go +++ b/btcjson/chainsvrresults.go @@ -90,28 +90,61 @@ type SoftForkDescription struct { // Bip9SoftForkDescription describes the current state of a defined BIP0009 // version bits soft-fork. type Bip9SoftForkDescription struct { - Status string `json:"status"` - Bit uint8 `json:"bit"` - StartTime int64 `json:"startTime"` - Timeout int64 `json:"timeout"` - Since int32 `json:"since"` + Status string `json:"status"` + Bit uint8 `json:"bit"` + StartTime1 int64 `json:"startTime"` + StartTime2 int64 `json:"start_time"` + Timeout int64 `json:"timeout"` + Since int32 `json:"since"` +} + +// StartTime returns the starting time of the softfork as a Unix epoch. +func (d *Bip9SoftForkDescription) StartTime() int64 { + if d.StartTime1 != 0 { + return d.StartTime1 + } + return d.StartTime2 +} + +// SoftForks describes the current softforks enabled by the backend. Softforks +// activated through BIP9 are grouped together separate from any other softforks +// with different activation types. +type SoftForks struct { + SoftForks []*SoftForkDescription `json:"softforks"` + Bip9SoftForks map[string]*Bip9SoftForkDescription `json:"bip9_softforks"` +} + +// UnifiedSoftForks describes a softforks in a general manner, irrespective of +// its activation type. This was a format introduced by bitcoind v0.19.0 +type UnifiedSoftFork struct { + Type string `json:"type"` + BIP9SoftForkDescription *Bip9SoftForkDescription `json:"bip9"` + Height int32 `json:"height"` + Active bool `json:"active"` +} + +// UnifiedSoftForks describes the current softforks enabled the by the backend +// in a unified manner, i.e, softforks with different activation types are +// grouped together. This was a format introduced by bitcoind v0.19.0 +type UnifiedSoftForks struct { + SoftForks map[string]*UnifiedSoftFork `json:"softforks"` } // GetBlockChainInfoResult models the data returned from the getblockchaininfo // command. type GetBlockChainInfoResult struct { - Chain string `json:"chain"` - Blocks int32 `json:"blocks"` - Headers int32 `json:"headers"` - BestBlockHash string `json:"bestblockhash"` - Difficulty float64 `json:"difficulty"` - MedianTime int64 `json:"mediantime"` - VerificationProgress float64 `json:"verificationprogress,omitempty"` - Pruned bool `json:"pruned"` - PruneHeight int32 `json:"pruneheight,omitempty"` - ChainWork string `json:"chainwork,omitempty"` - SoftForks []*SoftForkDescription `json:"softforks"` - Bip9SoftForks map[string]*Bip9SoftForkDescription `json:"bip9_softforks"` + Chain string `json:"chain"` + Blocks int32 `json:"blocks"` + Headers int32 `json:"headers"` + BestBlockHash string `json:"bestblockhash"` + Difficulty float64 `json:"difficulty"` + MedianTime int64 `json:"mediantime"` + VerificationProgress float64 `json:"verificationprogress,omitempty"` + Pruned bool `json:"pruned"` + PruneHeight int32 `json:"pruneheight,omitempty"` + ChainWork string `json:"chainwork,omitempty"` + *SoftForks + *UnifiedSoftForks } // GetBlockTemplateResultTx models the transactions field of the diff --git a/integration/bip0009_test.go b/integration/bip0009_test.go index 181c8983..df3721b1 100644 --- a/integration/bip0009_test.go +++ b/integration/bip0009_test.go @@ -102,7 +102,7 @@ func assertSoftForkStatus(r *rpctest.Harness, t *testing.T, forkKey string, stat } // Ensure the key is available. - desc, ok := info.Bip9SoftForks[forkKey] + desc, ok := info.SoftForks.Bip9SoftForks[forkKey] if !ok { _, _, line, _ := runtime.Caller(1) t.Fatalf("assertion failed at line %d: softfork status for %q "+ diff --git a/rpcclient/chain.go b/rpcclient/chain.go index c2166891..996d8045 100644 --- a/rpcclient/chain.go +++ b/rpcclient/chain.go @@ -253,16 +253,15 @@ func (c *Client) GetDifficulty() (float64, error) { // FutureGetBlockChainInfoResult is a promise to deliver the result of a // GetBlockChainInfoAsync RPC invocation (or an applicable error). -type FutureGetBlockChainInfoResult chan *response - -// Receive waits for the response promised by the future and returns chain info -// result provided by the server. -func (r FutureGetBlockChainInfoResult) Receive() (*btcjson.GetBlockChainInfoResult, error) { - res, err := receiveFuture(r) - if err != nil { - return nil, err - } +type FutureGetBlockChainInfoResult struct { + client *Client + Response chan *response +} +// unmarshalPartialGetBlockChainInfoResult unmarshals the response into an +// instance of GetBlockChainInfoResult without populating the SoftForks and +// UnifiedSoftForks fields. +func unmarshalPartialGetBlockChainInfoResult(res []byte) (*btcjson.GetBlockChainInfoResult, error) { var chainInfo btcjson.GetBlockChainInfoResult if err := json.Unmarshal(res, &chainInfo); err != nil { return nil, err @@ -270,6 +269,59 @@ func (r FutureGetBlockChainInfoResult) Receive() (*btcjson.GetBlockChainInfoResu return &chainInfo, nil } +// unmarshalGetBlockChainInfoResultSoftForks properly unmarshals the softforks +// related fields into the GetBlockChainInfoResult instance. +func unmarshalGetBlockChainInfoResultSoftForks(chainInfo *btcjson.GetBlockChainInfoResult, + version BackendVersion, res []byte) error { + + switch version { + // Versions of bitcoind on or after v0.19.0 use the unified format. + case BitcoindPost19: + var softForks btcjson.UnifiedSoftForks + if err := json.Unmarshal(res, &softForks); err != nil { + return err + } + chainInfo.UnifiedSoftForks = &softForks + + // All other versions use the original format. + default: + var softForks btcjson.SoftForks + if err := json.Unmarshal(res, &softForks); err != nil { + return err + } + chainInfo.SoftForks = &softForks + } + + return nil +} + +// Receive waits for the response promised by the future and returns chain info +// result provided by the server. +func (r FutureGetBlockChainInfoResult) Receive() (*btcjson.GetBlockChainInfoResult, error) { + res, err := receiveFuture(r.Response) + if err != nil { + return nil, err + } + chainInfo, err := unmarshalPartialGetBlockChainInfoResult(res) + if err != nil { + return nil, err + } + + // Inspect the version to determine how we'll need to parse the + // softforks from the response. + version, err := r.client.BackendVersion() + if err != nil { + return nil, err + } + + err = unmarshalGetBlockChainInfoResultSoftForks(chainInfo, version, res) + if err != nil { + return nil, err + } + + return chainInfo, nil +} + // GetBlockChainInfoAsync returns an instance of a type that can be used to get // the result of the RPC at some future time by invoking the Receive function // on the returned instance. @@ -277,7 +329,10 @@ func (r FutureGetBlockChainInfoResult) Receive() (*btcjson.GetBlockChainInfoResu // See GetBlockChainInfo for the blocking version and more details. func (c *Client) GetBlockChainInfoAsync() FutureGetBlockChainInfoResult { cmd := btcjson.NewGetBlockChainInfoCmd() - return c.sendCmd(cmd) + return FutureGetBlockChainInfoResult{ + client: c, + Response: c.sendCmd(cmd), + } } // GetBlockChainInfo returns information related to the processing state of diff --git a/rpcclient/chain_test.go b/rpcclient/chain_test.go new file mode 100644 index 00000000..e32d547c --- /dev/null +++ b/rpcclient/chain_test.go @@ -0,0 +1,92 @@ +package rpcclient + +import "testing" + +// TestUnmarshalGetBlockChainInfoResult ensures that the SoftForks and +// UnifiedSoftForks fields of GetBlockChainInfoResult are properly unmarshaled +// when using the expected backend version. +func TestUnmarshalGetBlockChainInfoResultSoftForks(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + version BackendVersion + res []byte + compatible bool + }{ + { + name: "bitcoind < 0.19.0 with separate softforks", + version: BitcoindPre19, + res: []byte(`{"softforks": [{"version": 2}]}`), + compatible: true, + }, + { + name: "bitcoind >= 0.19.0 with separate softforks", + version: BitcoindPost19, + res: []byte(`{"softforks": [{"version": 2}]}`), + compatible: false, + }, + { + name: "bitcoind < 0.19.0 with unified softforks", + version: BitcoindPre19, + res: []byte(`{"softforks": {"segwit": {"type": "bip9"}}}`), + compatible: false, + }, + { + name: "bitcoind >= 0.19.0 with unified softforks", + version: BitcoindPost19, + res: []byte(`{"softforks": {"segwit": {"type": "bip9"}}}`), + compatible: true, + }, + } + + for _, test := range tests { + success := t.Run(test.name, func(t *testing.T) { + // We'll start by unmarshaling the JSON into a struct. + // The SoftForks and UnifiedSoftForks field should not + // be set yet, as they are unmarshaled within a + // different function. + info, err := unmarshalPartialGetBlockChainInfoResult(test.res) + if err != nil { + t.Fatal(err) + } + if info.SoftForks != nil { + t.Fatal("expected SoftForks to be empty") + } + if info.UnifiedSoftForks != nil { + t.Fatal("expected UnifiedSoftForks to be empty") + } + + // Proceed to unmarshal the softforks of the response + // with the expected version. If the version is + // incompatible with the response, then this should + // fail. + err = unmarshalGetBlockChainInfoResultSoftForks( + info, test.version, test.res, + ) + if test.compatible && err != nil { + t.Fatalf("unable to unmarshal softforks: %v", err) + } + if !test.compatible && err == nil { + t.Fatal("expected to not unmarshal softforks") + } + if !test.compatible { + return + } + + // If the version is compatible with the response, we + // should expect to see the proper softforks field set. + if test.version == BitcoindPost19 && + info.SoftForks != nil { + t.Fatal("expected SoftForks to be empty") + } + if test.version == BitcoindPre19 && + info.UnifiedSoftForks != nil { + t.Fatal("expected UnifiedSoftForks to be empty") + } + }) + if !success { + return + } + } +} diff --git a/rpcclient/infrastructure.go b/rpcclient/infrastructure.go index 27f7f21f..7a8f1885 100644 --- a/rpcclient/infrastructure.go +++ b/rpcclient/infrastructure.go @@ -19,6 +19,7 @@ import ( "net" "net/http" "net/url" + "strings" "sync" "sync/atomic" "time" @@ -103,6 +104,22 @@ type jsonRequest struct { responseChan chan *response } +// BackendVersion represents the version of the backend the client is currently +// connected to. +type BackendVersion uint8 + +const ( + // BitcoindPre19 represents a bitcoind version before 0.19.0. + BitcoindPre19 BackendVersion = iota + + // BitcoindPost19 represents a bitcoind version equal to or greater than + // 0.19.0. + BitcoindPost19 + + // Btcd represents a catch-all btcd version. + Btcd +) + // Client represents a Bitcoin RPC client which allows easy access to the // various RPC methods available on a Bitcoin RPC server. Each of the wrapper // functions handle the details of converting the passed and return types to and @@ -129,6 +146,11 @@ type Client struct { // POST mode. httpClient *http.Client + // backendVersion is the version of the backend the client is currently + // connected to. This should be retrieved through GetVersion. + backendVersionMu sync.Mutex + backendVersion *BackendVersion + // mtx is a mutex to protect access to connection related fields. mtx sync.Mutex @@ -659,6 +681,12 @@ out: log.Infof("Reestablished connection to RPC server %s", c.config.Host) + // Reset the version in case the backend was + // disconnected due to an upgrade. + c.backendVersionMu.Lock() + c.backendVersion = nil + c.backendVersionMu.Unlock() + // Reset the connection state and signal the reconnect // has happened. c.wsConn = wsConn @@ -1332,3 +1360,84 @@ func (c *Client) Connect(tries int) error { // All connection attempts failed, so return the last error. return err } + +const ( + // bitcoind19Str is the string representation of bitcoind v0.19.0. + bitcoind19Str = "0.19.0" + + // bitcoindVersionPrefix specifies the prefix included in every bitcoind + // version exposed through GetNetworkInfo. + bitcoindVersionPrefix = "/Satoshi:" + + // bitcoindVersionSuffix specifies the suffix included in every bitcoind + // version exposed through GetNetworkInfo. + bitcoindVersionSuffix = "/" +) + +// parseBitcoindVersion parses the bitcoind version from its string +// representation. +func parseBitcoindVersion(version string) BackendVersion { + // Trim the version of its prefix and suffix to determine the + // appropriate version number. + version = strings.TrimPrefix( + strings.TrimSuffix(version, bitcoindVersionSuffix), + bitcoindVersionPrefix, + ) + switch { + case version < bitcoind19Str: + return BitcoindPre19 + default: + return BitcoindPost19 + } +} + +// BackendVersion retrieves the version of the backend the client is currently +// connected to. +func (c *Client) BackendVersion() (BackendVersion, error) { + c.backendVersionMu.Lock() + defer c.backendVersionMu.Unlock() + + if c.backendVersion != nil { + return *c.backendVersion, nil + } + + // We'll start by calling GetInfo. This method doesn't exist for + // bitcoind nodes as of v0.16.0, so we'll assume the client is connected + // to a btcd backend if it does exist. + info, err := c.GetInfo() + + switch err := err.(type) { + // Parse the btcd version and cache it. + case nil: + log.Debugf("Detected btcd version: %v", info.Version) + version := Btcd + c.backendVersion = &version + return *c.backendVersion, nil + + // Inspect the RPC error to ensure the method was not found, otherwise + // we actually ran into an error. + case *btcjson.RPCError: + if err.Code != btcjson.ErrRPCMethodNotFound.Code { + return 0, fmt.Errorf("unable to detect btcd version: "+ + "%v", err) + } + + default: + return 0, fmt.Errorf("unable to detect btcd version: %v", err) + } + + // Since the GetInfo method was not found, we assume the client is + // connected to a bitcoind backend, which exposes its version through + // GetNetworkInfo. + networkInfo, err := c.GetNetworkInfo() + if err != nil { + return 0, fmt.Errorf("unable to detect bitcoind version: %v", err) + } + + // Parse the bitcoind version and cache it. + log.Debugf("Detected bitcoind version: %v", networkInfo.SubVersion) + version := parseBitcoindVersion(networkInfo.SubVersion) + c.backendVersion = &version + + return *c.backendVersion, nil +} diff --git a/rpcclient/net.go b/rpcclient/net.go index 3b166cb9..6eb73625 100644 --- a/rpcclient/net.go +++ b/rpcclient/net.go @@ -244,6 +244,43 @@ func (c *Client) Ping() error { return c.PingAsync().Receive() } +// FutureGetNetworkInfoResult is a future promise to deliver the result of a +// GetNetworkInfoAsync RPC invocation (or an applicable error). +type FutureGetNetworkInfoResult chan *response + +// Receive waits for the response promised by the future and returns data about +// the current network. +func (r FutureGetNetworkInfoResult) Receive() (*btcjson.GetNetworkInfoResult, error) { + res, err := receiveFuture(r) + if err != nil { + return nil, err + } + + // Unmarshal result as an array of getpeerinfo result objects. + var networkInfo btcjson.GetNetworkInfoResult + err = json.Unmarshal(res, &networkInfo) + if err != nil { + return nil, err + } + + return &networkInfo, nil +} + +// GetNetworkInfoAsync returns an instance of a type that can be used to get the +// result of the RPC at some future time by invoking the Receive function on the +// returned instance. +// +// See GetNetworkInfo for the blocking version and more details. +func (c *Client) GetNetworkInfoAsync() FutureGetNetworkInfoResult { + cmd := btcjson.NewGetNetworkInfoCmd() + return c.sendCmd(cmd) +} + +// GetNetworkInfo returns data about the current network. +func (c *Client) GetNetworkInfo() (*btcjson.GetNetworkInfoResult, error) { + return c.GetNetworkInfoAsync().Receive() +} + // FutureGetPeerInfoResult is a future promise to deliver the result of a // GetPeerInfoAsync RPC invocation (or an applicable error). type FutureGetPeerInfoResult chan *response diff --git a/rpcclient/rawtransactions.go b/rpcclient/rawtransactions.go index e886f225..4bf4ee57 100644 --- a/rpcclient/rawtransactions.go +++ b/rpcclient/rawtransactions.go @@ -15,6 +15,12 @@ import ( "github.com/btcsuite/btcutil" ) +const ( + // defaultMaxFeeRate is the default maximum fee rate in sat/KB enforced + // by bitcoind v0.19.0 or after for transaction broadcast. + defaultMaxFeeRate = btcutil.SatoshiPerBitcoin / 10 +) + // SigHashType enumerates the available signature hashing types that the // SignRawTransaction function accepts. type SigHashType string @@ -296,7 +302,31 @@ func (c *Client) SendRawTransactionAsync(tx *wire.MsgTx, allowHighFees bool) Fut txHex = hex.EncodeToString(buf.Bytes()) } - cmd := btcjson.NewSendRawTransactionCmd(txHex, &allowHighFees) + // Due to differences in the sendrawtransaction API for different + // backends, we'll need to inspect our version and construct the + // appropriate request. + version, err := c.BackendVersion() + if err != nil { + return newFutureError(err) + } + + var cmd *btcjson.SendRawTransactionCmd + switch version { + // Starting from bitcoind v0.19.0, the MaxFeeRate field should be used. + case BitcoindPost19: + // Using a 0 MaxFeeRate is interpreted as a maximum fee rate not + // being enforced by bitcoind. + var maxFeeRate int32 + if !allowHighFees { + maxFeeRate = defaultMaxFeeRate + } + cmd = btcjson.NewBitcoindSendRawTransactionCmd(txHex, maxFeeRate) + + // Otherwise, use the AllowHighFees field. + default: + cmd = btcjson.NewSendRawTransactionCmd(txHex, &allowHighFees) + } + return c.sendCmd(cmd) } diff --git a/rpcserver.go b/rpcserver.go index 097ba2ec..e762cc1a 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -1198,14 +1198,16 @@ func handleGetBlockChainInfo(s *rpcServer, cmd interface{}, closeChan <-chan str Difficulty: getDifficultyRatio(chainSnapshot.Bits, params), MedianTime: chainSnapshot.MedianTime.Unix(), Pruned: false, - Bip9SoftForks: make(map[string]*btcjson.Bip9SoftForkDescription), + SoftForks: &btcjson.SoftForks{ + Bip9SoftForks: make(map[string]*btcjson.Bip9SoftForkDescription), + }, } // Next, populate the response with information describing the current // status of soft-forks deployed via the super-majority block // signalling mechanism. height := chainSnapshot.Height - chainInfo.SoftForks = []*btcjson.SoftForkDescription{ + chainInfo.SoftForks.SoftForks = []*btcjson.SoftForkDescription{ { ID: "bip34", Version: 2, @@ -1281,11 +1283,11 @@ func handleGetBlockChainInfo(s *rpcServer, cmd interface{}, closeChan <-chan str // Finally, populate the soft-fork description with all the // information gathered above. - chainInfo.Bip9SoftForks[forkName] = &btcjson.Bip9SoftForkDescription{ - Status: strings.ToLower(statusString), - Bit: deploymentDetails.BitNumber, - StartTime: int64(deploymentDetails.StartTime), - Timeout: int64(deploymentDetails.ExpireTime), + chainInfo.SoftForks.Bip9SoftForks[forkName] = &btcjson.Bip9SoftForkDescription{ + Status: strings.ToLower(statusString), + Bit: deploymentDetails.BitNumber, + StartTime2: int64(deploymentDetails.StartTime), + Timeout: int64(deploymentDetails.ExpireTime), } } diff --git a/rpcserverhelp.go b/rpcserverhelp.go index e7637db6..cc6935b5 100644 --- a/rpcserverhelp.go +++ b/rpcserverhelp.go @@ -172,21 +172,18 @@ var helpDescsEnUS = map[string]string{ "getblockchaininfo--synopsis": "Returns information about the current blockchain state and the status of any active soft-fork deployments.", // GetBlockChainInfoResult help. - "getblockchaininforesult-chain": "The name of the chain the daemon is on (testnet, mainnet, etc)", - "getblockchaininforesult-blocks": "The number of blocks in the best known chain", - "getblockchaininforesult-headers": "The number of headers that we've gathered for in the best known chain", - "getblockchaininforesult-bestblockhash": "The block hash for the latest block in the main chain", - "getblockchaininforesult-difficulty": "The current chain difficulty", - "getblockchaininforesult-mediantime": "The median time from the PoV of the best block in the chain", - "getblockchaininforesult-verificationprogress": "An estimate for how much of the best chain we've verified", - "getblockchaininforesult-pruned": "A bool that indicates if the node is pruned or not", - "getblockchaininforesult-pruneheight": "The lowest block retained in the current pruned chain", - "getblockchaininforesult-chainwork": "The total cumulative work in the best chain", - "getblockchaininforesult-softforks": "The status of the super-majority soft-forks", - "getblockchaininforesult-bip9_softforks": "JSON object describing active BIP0009 deployments", - "getblockchaininforesult-bip9_softforks--key": "bip9_softforks", - "getblockchaininforesult-bip9_softforks--value": "An object describing a particular BIP009 deployment", - "getblockchaininforesult-bip9_softforks--desc": "The status of any defined BIP0009 soft-fork deployments", + "getblockchaininforesult-chain": "The name of the chain the daemon is on (testnet, mainnet, etc)", + "getblockchaininforesult-blocks": "The number of blocks in the best known chain", + "getblockchaininforesult-headers": "The number of headers that we've gathered for in the best known chain", + "getblockchaininforesult-bestblockhash": "The block hash for the latest block in the main chain", + "getblockchaininforesult-difficulty": "The current chain difficulty", + "getblockchaininforesult-mediantime": "The median time from the PoV of the best block in the chain", + "getblockchaininforesult-verificationprogress": "An estimate for how much of the best chain we've verified", + "getblockchaininforesult-pruned": "A bool that indicates if the node is pruned or not", + "getblockchaininforesult-pruneheight": "The lowest block retained in the current pruned chain", + "getblockchaininforesult-chainwork": "The total cumulative work in the best chain", + "getblockchaininforesult-softforks": "The status of the super-majority soft-forks", + "getblockchaininforesult-unifiedsoftforks": "The status of the super-majority soft-forks used by bitcoind on or after v0.19.0", // SoftForkDescription help. "softforkdescription-reject": "The current activation status of the softfork", @@ -194,6 +191,19 @@ var helpDescsEnUS = map[string]string{ "softforkdescription-id": "The string identifier for the soft fork", "-status": "A bool which indicates if the soft fork is active", + // SoftForks help. + "softforks-softforks": "The status of the super-majority soft-forks", + "softforks-bip9_softforks": "JSON object describing active BIP0009 deployments", + "softforks-bip9_softforks--key": "bip9_softforks", + "softforks-bip9_softforks--value": "An object describing a particular BIP009 deployment", + "softforks-bip9_softforks--desc": "The status of any defined BIP0009 soft-fork deployments", + + // UnifiedSoftForks help. + "unifiedsoftforks-softforks": "The status of the super-majority soft-forks used by bitcoind on or after v0.19.0", + "unifiedsoftforks-softforks--key": "softforks", + "unifiedsoftforks-softforks--value": "An object describing an active softfork deployment used by bitcoind on or after v0.19.0", + "unifiedsoftforks-softforks--desc": "JSON object describing an active softfork deployment used by bitcoind on or after v0.19.0", + // TxRawResult help. "txrawresult-hex": "Hex-encoded transaction", "txrawresult-txid": "The hash of the transaction", @@ -542,6 +552,7 @@ var helpDescsEnUS = map[string]string{ "sendrawtransaction--synopsis": "Submits the serialized, hex-encoded transaction to the local peer and relays it to the network.", "sendrawtransaction-hextx": "Serialized, hex-encoded signed transaction", "sendrawtransaction-allowhighfees": "Whether or not to allow insanely high fees (btcd does not yet implement this parameter, so it has no effect)", + "sendrawtransaction-maxfeerate": "Used by bitcoind on or after v0.19.0", "sendrawtransaction--result0": "The hash of the transaction", // SetGenerateCmd help.