Skip to content

Commit

Permalink
feat: add evo quorum platformsign command (#23)
Browse files Browse the repository at this point in the history
* chore: address linter issues

* feat: add quotum platformsign command

* feat: add  Client.QuorumPlatformSignAsync and QuorumPlatformSign
  • Loading branch information
lklimek authored Jul 23, 2024
1 parent fc8c009 commit ae5027b
Show file tree
Hide file tree
Showing 8 changed files with 170 additions and 28 deletions.
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

0 comments on commit ae5027b

Please sign in to comment.