btcjson+rpcserverhelp: restore bitcoind compatibility

The PR #1594 introduced a change that made the order of parameters
relevant, if one of them is nil. This makes it harder to be backward
compatible with the same JSON message if an existing parameter in
bitcoind was re-purposed to have a different meaning.
This commit is contained in:
Oliver Gugger 2020-11-12 10:08:54 +01:00
parent 65d2b7a18c
commit 9e8bb3eddb
No known key found for this signature in database
GPG key ID: 8E4256593F177720
3 changed files with 93 additions and 19 deletions

View file

@ -11,6 +11,7 @@ import (
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"fmt" "fmt"
"reflect"
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
) )
@ -819,11 +820,60 @@ func NewSearchRawTransactionsCmd(address string, verbose, skip, count *int, vinE
} }
} }
// AllowHighFeesOrMaxFeeRate defines a type that can either be the legacy
// allowhighfees boolean field or the new maxfeerate int field.
type AllowHighFeesOrMaxFeeRate struct {
Value interface{}
}
// String returns the string representation of this struct, used for printing
// the marshaled default value in the help text.
func (a AllowHighFeesOrMaxFeeRate) String() string {
b, _ := a.MarshalJSON()
return string(b)
}
// MarshalJSON implements the json.Marshaler interface
func (a AllowHighFeesOrMaxFeeRate) MarshalJSON() ([]byte, error) {
// The default value is false which only works with the legacy versions.
if a.Value == nil ||
(reflect.ValueOf(a.Value).Kind() == reflect.Ptr &&
reflect.ValueOf(a.Value).IsNil()) {
return json.Marshal(false)
}
return json.Marshal(a.Value)
}
// UnmarshalJSON implements the json.Unmarshaler interface
func (a *AllowHighFeesOrMaxFeeRate) UnmarshalJSON(data []byte) error {
if len(data) == 0 {
return nil
}
var unmarshalled interface{}
if err := json.Unmarshal(data, &unmarshalled); err != nil {
return err
}
switch v := unmarshalled.(type) {
case bool:
a.Value = Bool(v)
case float64:
a.Value = Int32(int32(v))
default:
return fmt.Errorf("invalid allowhighfees or maxfeerate value: "+
"%v", unmarshalled)
}
return nil
}
// SendRawTransactionCmd defines the sendrawtransaction JSON-RPC command. // SendRawTransactionCmd defines the sendrawtransaction JSON-RPC command.
type SendRawTransactionCmd struct { type SendRawTransactionCmd struct {
HexTx string HexTx string
AllowHighFees *bool `jsonrpcdefault:"false"` FeeSetting *AllowHighFeesOrMaxFeeRate `jsonrpcdefault:"false"`
MaxFeeRate *int32
} }
// NewSendRawTransactionCmd returns a new instance which can be used to issue a // NewSendRawTransactionCmd returns a new instance which can be used to issue a
@ -834,7 +884,9 @@ type SendRawTransactionCmd struct {
func NewSendRawTransactionCmd(hexTx string, allowHighFees *bool) *SendRawTransactionCmd { func NewSendRawTransactionCmd(hexTx string, allowHighFees *bool) *SendRawTransactionCmd {
return &SendRawTransactionCmd{ return &SendRawTransactionCmd{
HexTx: hexTx, HexTx: hexTx,
AllowHighFees: allowHighFees, FeeSetting: &AllowHighFeesOrMaxFeeRate{
Value: allowHighFees,
},
} }
} }
@ -845,7 +897,9 @@ func NewSendRawTransactionCmd(hexTx string, allowHighFees *bool) *SendRawTransac
func NewBitcoindSendRawTransactionCmd(hexTx string, maxFeeRate int32) *SendRawTransactionCmd { func NewBitcoindSendRawTransactionCmd(hexTx string, maxFeeRate int32) *SendRawTransactionCmd {
return &SendRawTransactionCmd{ return &SendRawTransactionCmd{
HexTx: hexTx, HexTx: hexTx,
MaxFeeRate: &maxFeeRate, FeeSetting: &AllowHighFeesOrMaxFeeRate{
Value: &maxFeeRate,
},
} }
} }

View file

@ -1224,21 +1224,23 @@ func TestChainSvrCmds(t *testing.T) {
{ {
name: "sendrawtransaction", name: "sendrawtransaction",
newCmd: func() (interface{}, error) { newCmd: func() (interface{}, error) {
return btcjson.NewCmd("sendrawtransaction", "1122") return btcjson.NewCmd("sendrawtransaction", "1122", &btcjson.AllowHighFeesOrMaxFeeRate{})
}, },
staticCmd: func() interface{} { staticCmd: func() interface{} {
return btcjson.NewSendRawTransactionCmd("1122", nil) return btcjson.NewSendRawTransactionCmd("1122", nil)
}, },
marshalled: `{"jsonrpc":"1.0","method":"sendrawtransaction","params":["1122"],"id":1}`, marshalled: `{"jsonrpc":"1.0","method":"sendrawtransaction","params":["1122",false],"id":1}`,
unmarshalled: &btcjson.SendRawTransactionCmd{ unmarshalled: &btcjson.SendRawTransactionCmd{
HexTx: "1122", HexTx: "1122",
AllowHighFees: btcjson.Bool(false), FeeSetting: &btcjson.AllowHighFeesOrMaxFeeRate{
Value: btcjson.Bool(false),
},
}, },
}, },
{ {
name: "sendrawtransaction optional", name: "sendrawtransaction optional",
newCmd: func() (interface{}, error) { newCmd: func() (interface{}, error) {
return btcjson.NewCmd("sendrawtransaction", "1122", false) return btcjson.NewCmd("sendrawtransaction", "1122", &btcjson.AllowHighFeesOrMaxFeeRate{Value: btcjson.Bool(false)})
}, },
staticCmd: func() interface{} { staticCmd: func() interface{} {
return btcjson.NewSendRawTransactionCmd("1122", btcjson.Bool(false)) return btcjson.NewSendRawTransactionCmd("1122", btcjson.Bool(false))
@ -1246,7 +1248,25 @@ func TestChainSvrCmds(t *testing.T) {
marshalled: `{"jsonrpc":"1.0","method":"sendrawtransaction","params":["1122",false],"id":1}`, marshalled: `{"jsonrpc":"1.0","method":"sendrawtransaction","params":["1122",false],"id":1}`,
unmarshalled: &btcjson.SendRawTransactionCmd{ unmarshalled: &btcjson.SendRawTransactionCmd{
HexTx: "1122", HexTx: "1122",
AllowHighFees: btcjson.Bool(false), FeeSetting: &btcjson.AllowHighFeesOrMaxFeeRate{
Value: btcjson.Bool(false),
},
},
},
{
name: "sendrawtransaction optional, bitcoind >= 0.19.0",
newCmd: func() (interface{}, error) {
return btcjson.NewCmd("sendrawtransaction", "1122", &btcjson.AllowHighFeesOrMaxFeeRate{Value: btcjson.Int32(1234)})
},
staticCmd: func() interface{} {
return btcjson.NewBitcoindSendRawTransactionCmd("1122", 1234)
},
marshalled: `{"jsonrpc":"1.0","method":"sendrawtransaction","params":["1122",1234],"id":1}`,
unmarshalled: &btcjson.SendRawTransactionCmd{
HexTx: "1122",
FeeSetting: &btcjson.AllowHighFeesOrMaxFeeRate{
Value: btcjson.Int32(1234),
},
}, },
}, },
{ {

View file

@ -563,9 +563,9 @@ var helpDescsEnUS = map[string]string{
// SendRawTransactionCmd help. // SendRawTransactionCmd help.
"sendrawtransaction--synopsis": "Submits the serialized, hex-encoded transaction to the local peer and relays it to the network.", "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-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-feesetting": "Whether or not to allow insanely high fees in bitcoind < v0.19.0 or the max fee rate for bitcoind v0.19.0 and later (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", "sendrawtransaction--result0": "The hash of the transaction",
"allowhighfeesormaxfeerate-value": "Either the boolean value for the allowhighfees parameter in bitcoind < v0.19.0 or the numerical value for the maxfeerate field in bitcoind v0.19.0 and later",
// SetGenerateCmd help. // SetGenerateCmd help.
"setgenerate--synopsis": "Set the server to generate coins (mine) or not.", "setgenerate--synopsis": "Set the server to generate coins (mine) or not.",