Skip to content
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
2 changes: 1 addition & 1 deletion btcjson/cmdparse.go
Original file line number Diff line number Diff line change
Expand Up @@ -480,7 +480,7 @@ func assignField(paramNum int, fieldName string, dest reflect.Value, src reflect

// String -> float of varying size.
case reflect.Float32, reflect.Float64:
srcFloat, err := strconv.ParseFloat(src.String(), 0)
srcFloat, err := strconv.ParseFloat(src.String(), 64)
if err != nil {
str := fmt.Sprintf("parameter #%d '%s' must "+
"parse to a %v", paramNum, fieldName,
Expand Down
40 changes: 33 additions & 7 deletions btcjson/dashevocmds.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,10 @@ type QuorumCmdSubCmd string

// Quorum commands https://dashcore.readme.io/docs/core-api-ref-remote-procedure-calls-evo#quorum
const (
QuorumSign QuorumCmdSubCmd = "sign"
QuorumVerify QuorumCmdSubCmd = "verify"
QuorumInfo QuorumCmdSubCmd = "info"
QuorumSign QuorumCmdSubCmd = "sign"
QuorumSignPlatform QuorumCmdSubCmd = "platformsign"
QuorumVerify QuorumCmdSubCmd = "verify"
QuorumInfo QuorumCmdSubCmd = "info"

// QuorumList lists all quorums
QuorumList QuorumCmdSubCmd = "list"
Expand Down Expand Up @@ -197,7 +198,7 @@ func (t LLMQType) Validate() error {

// QuorumCmd defines the quorum JSON-RPC command.
type QuorumCmd struct {
SubCmd QuorumCmdSubCmd `jsonrpcusage:"\"list|info|dkgstatus|sign|getrecsig|hasrecsig|isconflicting|memberof|selectquorum\""`
SubCmd QuorumCmdSubCmd `jsonrpcusage:"\"list|info|dkgstatus|sign|platformsign|getrecsig|hasrecsig|isconflicting|memberof|selectquorum\""`

LLMQType *LLMQType `json:",omitempty"`
RequestID *string `json:",omitempty"`
Expand Down Expand Up @@ -229,6 +230,22 @@ func NewQuorumSignCmd(quorumType LLMQType, requestID, messageHash, quorumHash st
return cmd
}

// NewQuorumPlatformSignCmd returns a new instance which can be used to issue a quorum
// JSON-RPC command.
func NewQuorumPlatformSignCmd(requestID, messageHash, quorumHash string, submit bool) *QuorumCmd {
cmd := &QuorumCmd{
SubCmd: QuorumSignPlatform,
RequestID: &requestID,
MessageHash: &messageHash,
}
if quorumHash == "" {
return cmd
}
cmd.QuorumHash = &quorumHash
cmd.Submit = &submit
return cmd
}

// NewQuorumVerifyCmd returns a new instance which can be used to issue a quorum
// JSON-RPC command.
func NewQuorumVerifyCmd(quorumType LLMQType, requestID string, messageHash string, signature string, quorumHash string) *QuorumCmd {
Expand Down Expand Up @@ -517,9 +534,10 @@ func (q *QuorumCmd) UnmarshalArgs(args []interface{}) error {
type unmarshalQuorumCmdFunc func(*QuorumCmd, []interface{}) error

var quorumCmdUnmarshalers = map[string]unmarshalQuorumCmdFunc{
"info": withQuorumUnmarshaler(quorumInfoUnmarshaler, validateQuorumArgs(3), unmarshalQuorumLLMQType),
"sign": withQuorumUnmarshaler(quorumSignUnmarshaler, validateQuorumArgs(5), unmarshalQuorumLLMQType),
"verify": withQuorumUnmarshaler(quorumVerifyUnmarshaler, validateQuorumArgs(5), unmarshalQuorumLLMQType),
string(QuorumInfo): withQuorumUnmarshaler(quorumInfoUnmarshaler, validateQuorumArgs(3), unmarshalQuorumLLMQType),
string(QuorumSign): withQuorumUnmarshaler(quorumSignUnmarshaler, validateQuorumArgs(5), unmarshalQuorumLLMQType),
string(QuorumSignPlatform): withQuorumUnmarshaler(quorumPlatformSignUnmarshaler, validateQuorumArgs(4)),
string(QuorumVerify): withQuorumUnmarshaler(quorumVerifyUnmarshaler, validateQuorumArgs(5), unmarshalQuorumLLMQType),
}

func unmarshalLLMQType(val interface{}) (LLMQType, error) {
Expand Down Expand Up @@ -551,6 +569,14 @@ func quorumSignUnmarshaler(q *QuorumCmd, args []interface{}) error {
return nil
}

func quorumPlatformSignUnmarshaler(q *QuorumCmd, args []interface{}) error {
q.RequestID = strPtr(args[0].(string))
q.MessageHash = strPtr(args[1].(string))
q.QuorumHash = strPtr(args[2].(string))
q.Submit = boolPtr(args[3].(bool))
return nil
}

func unmarshalQuorumLLMQType(next unmarshalQuorumCmdFunc) unmarshalQuorumCmdFunc {
return func(q *QuorumCmd, args []interface{}) error {
val, err := unmarshalLLMQType(args[0])
Expand Down
27 changes: 26 additions & 1 deletion btcjson/dashevocmds_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func pLLMQType(l btcjson.LLMQType) *btcjson.LLMQType { return &l }
// into valid results include handling of optional fields being omitted in the
// marshalled command, while optional fields with defaults have the default
// assigned on unmarshalled commands.
func TestdashpayCmds(t *testing.T) {
func TestDashpayCmds(t *testing.T) {
t.Parallel()

testID := 1
Expand Down Expand Up @@ -61,6 +61,31 @@ func TestdashpayCmds(t *testing.T) {
Submit: pBool(false),
},
},
{
name: "quorum platformsign",
newCmd: func() (interface{}, error) {
return btcjson.NewCmd("quorum", "platformsign",
"0067c4fd779a195a95b267e263c631f71f83f8d5e6191091289d114012b373a1",
"ce490ca26cad6f1749ff9b977fe0fe4ece4391166f69be75c4619bc94b184dbc",
"6f1018f54507606069303fd16257434073c6f374729b0090bb9dbbe629241236",
false)
},
staticCmd: func() interface{} {
return btcjson.NewQuorumPlatformSignCmd(
"0067c4fd779a195a95b267e263c631f71f83f8d5e6191091289d114012b373a1",
"ce490ca26cad6f1749ff9b977fe0fe4ece4391166f69be75c4619bc94b184dbc",
"6f1018f54507606069303fd16257434073c6f374729b0090bb9dbbe629241236",
false)
},
marshalled: `{"jsonrpc":"1.0","method":"quorum","params":["platformsign","0067c4fd779a195a95b267e263c631f71f83f8d5e6191091289d114012b373a1","ce490ca26cad6f1749ff9b977fe0fe4ece4391166f69be75c4619bc94b184dbc","6f1018f54507606069303fd16257434073c6f374729b0090bb9dbbe629241236",false],"id":1}`,
unmarshalled: &btcjson.QuorumCmd{
SubCmd: "platformsign",
RequestID: pString("0067c4fd779a195a95b267e263c631f71f83f8d5e6191091289d114012b373a1"),
MessageHash: pString("ce490ca26cad6f1749ff9b977fe0fe4ece4391166f69be75c4619bc94b184dbc"),
QuorumHash: pString("6f1018f54507606069303fd16257434073c6f374729b0090bb9dbbe629241236"),
Submit: pBool(false),
},
},
{
name: "quorum info",
newCmd: func() (interface{}, error) {
Expand Down
28 changes: 15 additions & 13 deletions btcjson/help.go
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ func resultTypeHelp(xT descLookupFunc, rt reflect.Type, fieldDescKey string) str
w.Init(&formatted, 0, 4, 1, ' ', 0)
for i, text := range results {
if i == len(results)-1 {
fmt.Fprintf(w, text)
fmt.Fprint(w, text)
} else {
fmt.Fprintln(w, text)
}
Expand Down Expand Up @@ -476,11 +476,12 @@ func isValidResultType(kind reflect.Kind) bool {
// an error will use the key in place of the description.
//
// The following outlines the required keys:
// "<method>--synopsis" Synopsis for the command
// "<method>-<lowerfieldname>" Description for each command argument
// "<typename>-<lowerfieldname>" Description for each object field
// "<method>--condition<#>" Description for each result condition
// "<method>--result<#>" Description for each primitive result num
//
// "<method>--synopsis" Synopsis for the command
// "<method>-<lowerfieldname>" Description for each command argument
// "<typename>-<lowerfieldname>" Description for each object field
// "<method>--condition<#>" Description for each result condition
// "<method>--result<#>" Description for each primitive result num
//
// Notice that the "special" keys synopsis, condition<#>, and result<#> are
// preceded by a double dash to ensure they don't conflict with field names.
Expand All @@ -492,16 +493,17 @@ func isValidResultType(kind reflect.Kind) bool {
// For example, consider the 'help' command itself. There are two possible
// returns depending on the provided parameters. So, the help would be
// generated by calling the function as follows:
// GenerateHelp("help", descs, (*string)(nil), (*string)(nil)).
//
// GenerateHelp("help", descs, (*string)(nil), (*string)(nil)).
//
// The following keys would then be required in the provided descriptions map:
//
// "help--synopsis": "Returns a list of all commands or help for ...."
// "help-command": "The command to retrieve help for",
// "help--condition0": "no command provided"
// "help--condition1": "command specified"
// "help--result0": "List of commands"
// "help--result1": "Help for specified command"
// "help--synopsis": "Returns a list of all commands or help for ...."
// "help-command": "The command to retrieve help for",
// "help--condition0": "no command provided"
// "help--condition1": "command specified"
// "help--result0": "List of commands"
// "help--result1": "Help for specified command"
func GenerateHelp(method string, descs map[string]string, resultTypes ...interface{}) (string, error) {
// Look up details about the provided method and error out if not
// registered.
Expand Down
14 changes: 10 additions & 4 deletions btcjson/help_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ func TestHelpReflectInternals(t *testing.T) {
name: "array of struct indent level 0",
reflectType: func() reflect.Type {
type s struct {
field int
field int //nolint:unused
}
return reflect.TypeOf([]s{})
}(),
Expand All @@ -192,7 +192,7 @@ func TestHelpReflectInternals(t *testing.T) {
name: "array of struct indent level 1",
reflectType: func() reflect.Type {
type s struct {
field int
field int //nolint:unused
}
return reflect.TypeOf([]s{})
}(),
Expand Down Expand Up @@ -309,7 +309,7 @@ func TestResultStructHelp(t *testing.T) {
name: "struct with primitive field",
reflectType: func() reflect.Type {
type s struct {
field int
field int //nolint:unused
}
return reflect.TypeOf(s{})
}(),
Expand All @@ -333,7 +333,7 @@ func TestResultStructHelp(t *testing.T) {
name: "struct with array of primitive field",
reflectType: func() reflect.Type {
type s struct {
field []int
field []int //nolint:unused
}
return reflect.TypeOf(s{})
}(),
Expand All @@ -344,9 +344,11 @@ func TestResultStructHelp(t *testing.T) {
{
name: "struct with sub-struct field",
reflectType: func() reflect.Type {
//nolint:unused
type s2 struct {
subField int
}
//nolint:unused
type s struct {
field s2
}
Expand All @@ -362,9 +364,11 @@ func TestResultStructHelp(t *testing.T) {
{
name: "struct with sub-struct field pointer",
reflectType: func() reflect.Type {
//nolint:unused
type s2 struct {
subField int
}
//nolint:unused
type s struct {
field *s2
}
Expand All @@ -380,9 +384,11 @@ func TestResultStructHelp(t *testing.T) {
{
name: "struct with array of structs field",
reflectType: func() reflect.Type {
//nolint:unused
type s2 struct {
subField int
}
//nolint:unused
type s struct {
field []s2
}
Expand Down
8 changes: 6 additions & 2 deletions btcjson/register_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,9 @@ func TestRegisterCmdErrors(t *testing.T) {
name: "embedded field",
method: "registertestcmd",
cmdFunc: func() interface{} {
type test struct{ int }
type test struct {
int //nolint:unused
}
return (*test)(nil)
},
err: btcjson.Error{ErrorCode: btcjson.ErrEmbeddedType},
Expand All @@ -112,7 +114,9 @@ func TestRegisterCmdErrors(t *testing.T) {
name: "unexported field",
method: "registertestcmd",
cmdFunc: func() interface{} {
type test struct{ a int }
type test struct {
a int //nolint:unused
}
return (*test)(nil)
},
err: btcjson.Error{ErrorCode: btcjson.ErrUnexportedField},
Expand Down
22 changes: 22 additions & 0 deletions rpcclient/evo.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,28 @@ func (c *Client) QuorumSign(quorumType btcjson.LLMQType, requestID, messageHash,
return c.QuorumSignAsync(quorumType, requestID, messageHash, quorumHash, submit).Receive()
}

// QuorumPlatformSign returns a future that can be used to get the result of the RPC at some future time by invoking the Receive function on the returned instance.
// It uses `quorum platformsign` RPC command.
func (c *Client) QuorumPlatformSignAsync(requestID, messageHash, quorumHash string, submit bool) FutureGetQuorumSignResult {
cmd := btcjson.NewQuorumPlatformSignCmd(requestID, messageHash, quorumHash, submit)

return FutureGetQuorumSignResult{
client: c,
Response: c.SendCmd(cmd),
}
}

// QuorumPlatformSign a quorum sign result containing a signature signed by the quorum in question.
// It uses `quorum platformsign` RPC command.
func (c *Client) QuorumPlatformSign(requestID, messageHash, quorumHash string, submit bool) (*btcjson.QuorumSignResultWithBool, error) {
cmd := btcjson.NewQuorumPlatformSignCmd(requestID, messageHash, quorumHash, submit)

return FutureGetQuorumSignResult{
client: c,
Response: c.SendCmd(cmd),
}.Receive()
}

// QuorumSignSubmit calls QuorumSign but only returns a boolean to match dash-cli
func (c *Client) QuorumSignSubmit(quorumType btcjson.LLMQType, requestID, messageHash, quorumHash string) (bool, error) {
r, err := c.QuorumSignAsync(quorumType, requestID, messageHash, quorumHash, true).Receive()
Expand Down
57 changes: 57 additions & 0 deletions rpcclient/evo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,63 @@ func TestQuorumSign(t *testing.T) {
t.Log("bool response:", bl)
}

func TestQuorumPlatformSign(t *testing.T) {
requestID := "abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234"
messageHash := "51c11d287dfa85aef3eebb5420834c8e443e01d15c0b0a8e397d67e2e51aa239"
proTxHash := "ec21749595a34d868cc366c0feefbd1cfaeb659c6acbc1e2e96fd1e714affa56"
submit := false

client, err := New(connCfg, nil)
if err != nil {
t.Fatal(err)
}
defer client.Shutdown()

client.httpClient.Transport = mockRoundTripperFunc(
[]btcjson.QuorumMemberOfResult{
{
Height: 264072,
Type: "llmq_400_60",
QuorumHash: "000004bfc56646880bfeb80a0b89ad955e557ead7b0f09bcc61e56c8473eaea9",
MinedBlock: "000006113a77b35a0ed606b08ecb8e37f1ac7e2d773c365bd07064a72ae9a61d",
QuorumPublicKey: "0644ff153b9b92c6a59e2adf4ef0b9836f7f6af05fe432ffdcb69bc9e300a2a70af4a8d9fc61323f6b81074d740033d2",
IsValidMember: false,
MemberIndex: 10,
},
},
expectBody(`{"jsonrpc":"1.0","method":"quorum","params":["memberof","ec21749595a34d868cc366c0feefbd1cfaeb659c6acbc1e2e96fd1e714affa56"],"id":1}`),
)
mo, err := client.QuorumMemberOf(proTxHash, 0)
if err != nil {
t.Fatal(err)
}

if len(mo) == 0 {
t.Fatal("not a member of any quorums")
}
quorumHash := mo[0].QuorumHash
quorumType := btcjson.GetLLMQType(mo[0].Type)
if quorumType == 0 {
t.Fatal("unknown quorum type", mo[0].Type)
}

client.httpClient.Transport = mockRoundTripperFunc(
btcjson.QuorumSignResultWithBool{Result: true},
expectBody(`{"jsonrpc":"1.0","method":"quorum","params":["platformsign","abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234","51c11d287dfa85aef3eebb5420834c8e443e01d15c0b0a8e397d67e2e51aa239","000004bfc56646880bfeb80a0b89ad955e557ead7b0f09bcc61e56c8473eaea9",false],"id":2}`),
)
result, err := client.QuorumPlatformSign(requestID, messageHash, quorumHash, submit)
if err != nil {
t.Fatal(err)
}

cli := &btcjson.QuorumSignResultWithBool{}
compareWithCliCommand(t, result, cli, "quorum", "platformsign", requestID, messageHash, quorumHash, strconv.FormatBool(submit))

client.httpClient.Transport = mockRoundTripperFunc(
btcjson.QuorumSignResultWithBool{Result: true},
expectBody(`{"jsonrpc":"1.0","method":"quorum","params":["platformsign",2,"abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234","51c11d287dfa85aef3eebb5420834c8e443e01d15c0b0a8e397d67e2e51aa239","000004bfc56646880bfeb80a0b89ad955e557ead7b0f09bcc61e56c8473eaea9",true],"id":3}`),
)
}
func TestQuorumGetRecSig(t *testing.T) {
client, err := New(connCfg, nil)
if err != nil {
Expand Down