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 all commits
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
12 changes: 12 additions & 0 deletions btcjson/chainsvrcmds.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
67 changes: 50 additions & 17 deletions btcjson/chainsvrresults.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
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
}
Loading