btcjson+rpcclient: support new unified softfork bitcoind format

This commit is contained in:
Wilmer Paulino 2019-10-29 18:50:46 -07:00
parent e89d4fca24
commit 266851e329
No known key found for this signature in database
GPG key ID: 6DF57B9F9514972F
6 changed files with 224 additions and 41 deletions

View file

@ -97,21 +97,45 @@ type Bip9SoftForkDescription struct {
Since int32 `json:"since"` 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 // GetBlockChainInfoResult models the data returned from the getblockchaininfo
// command. // command.
type GetBlockChainInfoResult struct { type GetBlockChainInfoResult struct {
Chain string `json:"chain"` Chain string `json:"chain"`
Blocks int32 `json:"blocks"` Blocks int32 `json:"blocks"`
Headers int32 `json:"headers"` Headers int32 `json:"headers"`
BestBlockHash string `json:"bestblockhash"` BestBlockHash string `json:"bestblockhash"`
Difficulty float64 `json:"difficulty"` Difficulty float64 `json:"difficulty"`
MedianTime int64 `json:"mediantime"` MedianTime int64 `json:"mediantime"`
VerificationProgress float64 `json:"verificationprogress,omitempty"` VerificationProgress float64 `json:"verificationprogress,omitempty"`
Pruned bool `json:"pruned"` Pruned bool `json:"pruned"`
PruneHeight int32 `json:"pruneheight,omitempty"` PruneHeight int32 `json:"pruneheight,omitempty"`
ChainWork string `json:"chainwork,omitempty"` ChainWork string `json:"chainwork,omitempty"`
SoftForks []*SoftForkDescription `json:"softforks"` *SoftForks
Bip9SoftForks map[string]*Bip9SoftForkDescription `json:"bip9_softforks"` *UnifiedSoftForks
} }
// GetBlockTemplateResultTx models the transactions field of the // GetBlockTemplateResultTx models the transactions field of the

View file

@ -102,7 +102,7 @@ func assertSoftForkStatus(r *rpctest.Harness, t *testing.T, forkKey string, stat
} }
// Ensure the key is available. // Ensure the key is available.
desc, ok := info.Bip9SoftForks[forkKey] desc, ok := info.SoftForks.Bip9SoftForks[forkKey]
if !ok { if !ok {
_, _, line, _ := runtime.Caller(1) _, _, line, _ := runtime.Caller(1)
t.Fatalf("assertion failed at line %d: softfork status for %q "+ t.Fatalf("assertion failed at line %d: softfork status for %q "+

View file

@ -253,16 +253,15 @@ func (c *Client) GetDifficulty() (float64, error) {
// FutureGetBlockChainInfoResult is a promise to deliver the result of a // FutureGetBlockChainInfoResult is a promise to deliver the result of a
// GetBlockChainInfoAsync RPC invocation (or an applicable error). // GetBlockChainInfoAsync RPC invocation (or an applicable error).
type FutureGetBlockChainInfoResult chan *response type FutureGetBlockChainInfoResult struct {
client *Client
// Receive waits for the response promised by the future and returns chain info Response chan *response
// result provided by the server. }
func (r FutureGetBlockChainInfoResult) Receive() (*btcjson.GetBlockChainInfoResult, error) {
res, err := receiveFuture(r)
if err != nil {
return nil, err
}
// 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 var chainInfo btcjson.GetBlockChainInfoResult
if err := json.Unmarshal(res, &chainInfo); err != nil { if err := json.Unmarshal(res, &chainInfo); err != nil {
return nil, err return nil, err
@ -270,6 +269,59 @@ func (r FutureGetBlockChainInfoResult) Receive() (*btcjson.GetBlockChainInfoResu
return &chainInfo, nil 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 // 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 // the result of the RPC at some future time by invoking the Receive function
// on the returned instance. // on the returned instance.
@ -277,7 +329,10 @@ func (r FutureGetBlockChainInfoResult) Receive() (*btcjson.GetBlockChainInfoResu
// See GetBlockChainInfo for the blocking version and more details. // See GetBlockChainInfo for the blocking version and more details.
func (c *Client) GetBlockChainInfoAsync() FutureGetBlockChainInfoResult { func (c *Client) GetBlockChainInfoAsync() FutureGetBlockChainInfoResult {
cmd := btcjson.NewGetBlockChainInfoCmd() cmd := btcjson.NewGetBlockChainInfoCmd()
return c.sendCmd(cmd) return FutureGetBlockChainInfoResult{
client: c,
Response: c.sendCmd(cmd),
}
} }
// GetBlockChainInfo returns information related to the processing state of // GetBlockChainInfo returns information related to the processing state of

92
rpcclient/chain_test.go Normal file
View file

@ -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
}
}
}

View file

@ -1198,14 +1198,16 @@ func handleGetBlockChainInfo(s *rpcServer, cmd interface{}, closeChan <-chan str
Difficulty: getDifficultyRatio(chainSnapshot.Bits, params), Difficulty: getDifficultyRatio(chainSnapshot.Bits, params),
MedianTime: chainSnapshot.MedianTime.Unix(), MedianTime: chainSnapshot.MedianTime.Unix(),
Pruned: false, 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 // Next, populate the response with information describing the current
// status of soft-forks deployed via the super-majority block // status of soft-forks deployed via the super-majority block
// signalling mechanism. // signalling mechanism.
height := chainSnapshot.Height height := chainSnapshot.Height
chainInfo.SoftForks = []*btcjson.SoftForkDescription{ chainInfo.SoftForks.SoftForks = []*btcjson.SoftForkDescription{
{ {
ID: "bip34", ID: "bip34",
Version: 2, Version: 2,
@ -1281,7 +1283,7 @@ func handleGetBlockChainInfo(s *rpcServer, cmd interface{}, closeChan <-chan str
// Finally, populate the soft-fork description with all the // Finally, populate the soft-fork description with all the
// information gathered above. // information gathered above.
chainInfo.Bip9SoftForks[forkName] = &btcjson.Bip9SoftForkDescription{ chainInfo.SoftForks.Bip9SoftForks[forkName] = &btcjson.Bip9SoftForkDescription{
Status: strings.ToLower(statusString), Status: strings.ToLower(statusString),
Bit: deploymentDetails.BitNumber, Bit: deploymentDetails.BitNumber,
StartTime: int64(deploymentDetails.StartTime), StartTime: int64(deploymentDetails.StartTime),

View file

@ -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.", "getblockchaininfo--synopsis": "Returns information about the current blockchain state and the status of any active soft-fork deployments.",
// GetBlockChainInfoResult help. // GetBlockChainInfoResult help.
"getblockchaininforesult-chain": "The name of the chain the daemon is on (testnet, mainnet, etc)", "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-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-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-bestblockhash": "The block hash for the latest block in the main chain",
"getblockchaininforesult-difficulty": "The current chain difficulty", "getblockchaininforesult-difficulty": "The current chain difficulty",
"getblockchaininforesult-mediantime": "The median time from the PoV of the best block in the chain", "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-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-pruned": "A bool that indicates if the node is pruned or not",
"getblockchaininforesult-pruneheight": "The lowest block retained in the current pruned chain", "getblockchaininforesult-pruneheight": "The lowest block retained in the current pruned chain",
"getblockchaininforesult-chainwork": "The total cumulative work in the best chain", "getblockchaininforesult-chainwork": "The total cumulative work in the best chain",
"getblockchaininforesult-softforks": "The status of the super-majority soft-forks", "getblockchaininforesult-softforks": "The status of the super-majority soft-forks",
"getblockchaininforesult-bip9_softforks": "JSON object describing active BIP0009 deployments", "getblockchaininforesult-unifiedsoftforks": "The status of the super-majority soft-forks used by bitcoind on or after v0.19.0",
"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",
// SoftForkDescription help. // SoftForkDescription help.
"softforkdescription-reject": "The current activation status of the softfork", "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", "softforkdescription-id": "The string identifier for the soft fork",
"-status": "A bool which indicates if the soft fork is active", "-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 help.
"txrawresult-hex": "Hex-encoded transaction", "txrawresult-hex": "Hex-encoded transaction",
"txrawresult-txid": "The hash of the transaction", "txrawresult-txid": "The hash of the transaction",