From a46f7b45abb3512c0a43c3c7f268162c48160a96 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Tue, 29 Oct 2019 18:49:37 -0700 Subject: [PATCH 1/8] rpcclient: add GetNetworkInfo method --- rpcclient/net.go | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/rpcclient/net.go b/rpcclient/net.go index 3b166cb934..6eb7362541 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 From bc21593480f27e1cdb320c5bbdfde3a382dc4b07 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Tue, 29 Oct 2019 19:38:38 -0700 Subject: [PATCH 2/8] server: remove peer from SyncManager on VerAckReceived Peers are now added to the SyncManager if we receive their verack, but we'd still attempt to remove them from the SyncManager if we didn't receive it. --- server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.go b/server.go index 1d7fdf9b71..a0fc8099ec 100644 --- a/server.go +++ b/server.go @@ -2058,7 +2058,7 @@ func (s *server) peerDoneHandler(sp *serverPeer) { s.donePeers <- sp // Only tell sync manager we are gone if we ever told it we existed. - if sp.VersionKnown() { + if sp.VerAckReceived() { s.syncManager.DonePeer(sp.Peer) // Evict any remaining orphans that were sent by the peer. From e89d4fca24a0dee0d89f913c54412f1e6635b1ff Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Tue, 29 Oct 2019 18:50:11 -0700 Subject: [PATCH 3/8] rpcclient: allow retrieval of backend version --- rpcclient/infrastructure.go | 109 ++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/rpcclient/infrastructure.go b/rpcclient/infrastructure.go index 27f7f21f1b..7a8f1885d1 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 +} From 266851e329e2089fd2e0f082ddc6002c920be879 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Tue, 29 Oct 2019 18:50:46 -0700 Subject: [PATCH 4/8] btcjson+rpcclient: support new unified softfork bitcoind format --- btcjson/chainsvrresults.go | 48 ++++++++++++++----- integration/bip0009_test.go | 2 +- rpcclient/chain.go | 67 ++++++++++++++++++++++++--- rpcclient/chain_test.go | 92 +++++++++++++++++++++++++++++++++++++ rpcserver.go | 8 ++-- rpcserverhelp.go | 40 ++++++++++------ 6 files changed, 220 insertions(+), 37 deletions(-) create mode 100644 rpcclient/chain_test.go diff --git a/btcjson/chainsvrresults.go b/btcjson/chainsvrresults.go index cc9c085531..257a6c90c3 100644 --- a/btcjson/chainsvrresults.go +++ b/btcjson/chainsvrresults.go @@ -97,21 +97,45 @@ type Bip9SoftForkDescription struct { Since int32 `json:"since"` } +// 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 181c8983ef..df3721b1e6 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 c21668918d..996d80458c 100644 --- a/rpcclient/chain.go +++ b/rpcclient/chain.go @@ -253,21 +253,73 @@ 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 +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 + } + 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) + res, err := receiveFuture(r.Response) + if err != nil { + return nil, err + } + chainInfo, err := unmarshalPartialGetBlockChainInfoResult(res) if err != nil { return nil, err } - var chainInfo btcjson.GetBlockChainInfoResult - if err := json.Unmarshal(res, &chainInfo); err != nil { + // 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 } - return &chainInfo, nil + + 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 @@ -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 0000000000..e32d547ce3 --- /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/rpcserver.go b/rpcserver.go index 097ba2ec84..0a7ab8ed89 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,7 +1283,7 @@ 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{ + chainInfo.SoftForks.Bip9SoftForks[forkName] = &btcjson.Bip9SoftForkDescription{ Status: strings.ToLower(statusString), Bit: deploymentDetails.BitNumber, StartTime: int64(deploymentDetails.StartTime), diff --git a/rpcserverhelp.go b/rpcserverhelp.go index e7637db69c..7da266eac1 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", From 93a84aa0143f6bc0787891ab2fe9b0beb36f4b22 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Tue, 29 Oct 2019 18:51:12 -0700 Subject: [PATCH 5/8] btcjson: use correct json tag for Bip9SoftForkDescription.StartTime --- btcjson/chainsvrresults.go | 19 ++++++++++++++----- rpcserver.go | 8 ++++---- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/btcjson/chainsvrresults.go b/btcjson/chainsvrresults.go index 257a6c90c3..7e6c710766 100644 --- a/btcjson/chainsvrresults.go +++ b/btcjson/chainsvrresults.go @@ -90,11 +90,20 @@ 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 diff --git a/rpcserver.go b/rpcserver.go index 0a7ab8ed89..e762cc1a9d 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -1284,10 +1284,10 @@ func handleGetBlockChainInfo(s *rpcServer, cmd interface{}, closeChan <-chan str // Finally, populate the soft-fork description with all the // information gathered above. chainInfo.SoftForks.Bip9SoftForks[forkName] = &btcjson.Bip9SoftForkDescription{ - Status: strings.ToLower(statusString), - Bit: deploymentDetails.BitNumber, - StartTime: int64(deploymentDetails.StartTime), - Timeout: int64(deploymentDetails.ExpireTime), + Status: strings.ToLower(statusString), + Bit: deploymentDetails.BitNumber, + StartTime2: int64(deploymentDetails.StartTime), + Timeout: int64(deploymentDetails.ExpireTime), } } From e2e5cc694dda523225cc01166867411ecb6c8477 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Tue, 29 Oct 2019 18:51:59 -0700 Subject: [PATCH 6/8] btcjson+rpcclient: support new bitcoind sendrawtransaction request --- btcjson/chainsvrcmds.go | 12 ++++++++++++ rpcclient/rawtransactions.go | 32 +++++++++++++++++++++++++++++++- rpcserverhelp.go | 1 + 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/btcjson/chainsvrcmds.go b/btcjson/chainsvrcmds.go index a5e5647112..406357bd06 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/rpcclient/rawtransactions.go b/rpcclient/rawtransactions.go index e886f2250d..4bf4ee57fc 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/rpcserverhelp.go b/rpcserverhelp.go index 7da266eac1..cc6935b512 100644 --- a/rpcserverhelp.go +++ b/rpcserverhelp.go @@ -552,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. From c2ca0a408e09db7f46b760c3cb8a280ede58d54a Mon Sep 17 00:00:00 2001 From: nsa Date: Fri, 8 Nov 2019 21:11:24 -0500 Subject: [PATCH 7/8] server: add addressesMtx to fix race condition --- server.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/server.go b/server.go index 1d7fdf9b71..786d9b2a2c 100644 --- a/server.go +++ b/server.go @@ -273,6 +273,7 @@ type serverPeer struct { sentAddrs bool isWhitelisted bool filter *bloom.Filter + addressesMtx sync.RWMutex knownAddresses map[string]struct{} banScore connmgr.DynamicBanScore quit chan struct{} @@ -305,14 +306,18 @@ func (sp *serverPeer) newestBlock() (*chainhash.Hash, int32, error) { // addKnownAddresses adds the given addresses to the set of known addresses to // the peer to prevent sending duplicate addresses. func (sp *serverPeer) addKnownAddresses(addresses []*wire.NetAddress) { + sp.addressesMtx.Lock() for _, na := range addresses { sp.knownAddresses[addrmgr.NetAddressKey(na)] = struct{}{} } + sp.addressesMtx.Unlock() } // addressKnown true if the given address is already known to the peer. func (sp *serverPeer) addressKnown(na *wire.NetAddress) bool { + sp.addressesMtx.RLock() _, exists := sp.knownAddresses[addrmgr.NetAddressKey(na)] + sp.addressesMtx.RUnlock() return exists } From f3ec13030e4e828869954472cbc51ac36bee5c1d Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 12 Nov 2019 18:27:49 -0800 Subject: [PATCH 8/8] btcd: bump version to v0.20.1-beta --- cmd/btcctl/version.go | 2 +- version.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/btcctl/version.go b/cmd/btcctl/version.go index 2195175c71..f65cacef7e 100644 --- a/cmd/btcctl/version.go +++ b/cmd/btcctl/version.go @@ -18,7 +18,7 @@ const semanticAlphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr const ( appMajor uint = 0 appMinor uint = 20 - appPatch uint = 0 + appPatch uint = 1 // appPreRelease MUST only contain characters from semanticAlphabet // per the semantic versioning spec. diff --git a/version.go b/version.go index 92fd60fdd4..fba55b5a37 100644 --- a/version.go +++ b/version.go @@ -18,7 +18,7 @@ const semanticAlphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr const ( appMajor uint = 0 appMinor uint = 20 - appPatch uint = 0 + appPatch uint = 1 // appPreRelease MUST only contain characters from semanticAlphabet // per the semantic versioning spec.