Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

btcjson+rpcclient: add compatibility for bitcoind v0.19.0 #1484

Merged
merged 5 commits into from
Nov 9, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
btcjson+rpcclient: support new unified softfork bitcoind format
  • Loading branch information
wpaulino committed Nov 6, 2019
commit 266851e329e2089fd2e0f082ddc6002c920be879
48 changes: 36 additions & 12 deletions btcjson/chainsvrresults.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion integration/bip0009_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 "+
Expand Down
67 changes: 61 additions & 6 deletions rpcclient/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
wpaulino marked this conversation as resolved.
Show resolved Hide resolved
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
Expand All @@ -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
Expand Down
92 changes: 92 additions & 0 deletions rpcclient/chain_test.go
Original file line number Diff line number Diff line change
@@ -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
}
}
Roasbeef marked this conversation as resolved.
Show resolved Hide resolved
}
8 changes: 5 additions & 3 deletions rpcserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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),
Expand Down
40 changes: 25 additions & 15 deletions rpcserverhelp.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,28 +172,38 @@ 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",
"softforkdescription-version": "The block version that signals enforcement of this softfork",
"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",
Expand Down