From f525d5bda1ad97d93d8a4519aafc476429d11e04 Mon Sep 17 00:00:00 2001 From: Dmitrii Golubev Date: Wed, 2 Mar 2022 10:00:19 +0100 Subject: [PATCH] fix: dashevo cmd (#10) * refactor: add an implementation of specific unmarshalers for quorum subtype * fix: update TestDashEvoCmds --- btcjson/cmdparse.go | 38 ++++++++++++- btcjson/dashevocmds.go | 110 ++++++++++++++++++++++++++++++++++-- btcjson/dashevocmds_test.go | 22 +++++--- btcjson/util.go | 13 +++++ 4 files changed, 167 insertions(+), 16 deletions(-) create mode 100644 btcjson/util.go diff --git a/btcjson/cmdparse.go b/btcjson/cmdparse.go index 132fcd01d2..55052fc6e6 100644 --- a/btcjson/cmdparse.go +++ b/btcjson/cmdparse.go @@ -143,6 +143,19 @@ func UnmarshalCmd(r *Request) (interface{}, error) { return nil, err } + cmd := rvp.Interface() + unmarshaler, ok := cmd.(Unmarshaler) + if ok { + // unmarshal every parameter item from json.RawMessage into a go type value + // result of this operation will be a slice of the interfaces + args, err := unmarshalArgItems(r.Params) + if err != nil { + return nil, err + } + err = unmarshaler.UnmarshalArgs(args) + return unmarshaler, err + } + // Loop through each of the struct fields and unmarshal the associated // parameter into them. for i := 0; i < numParams; i++ { @@ -555,6 +568,13 @@ func NewCmd(method string, args ...interface{}) (interface{}, error) { rv := rvp.Elem() rt := rtp.Elem() + cmd := rvp.Interface() + unmarshaler, ok := cmd.(Unmarshaler) + if ok { + err := unmarshaler.UnmarshalArgs(args) + return unmarshaler, err + } + // Loop through each of the struct fields and assign the associated // parameter into them after checking its type validity. for i := 0; i < numParams; i++ { @@ -568,5 +588,21 @@ func NewCmd(method string, args ...interface{}) (interface{}, error) { } } - return rvp.Interface(), nil + return cmd, nil +} + +// Unmarshaler is an interface for a specific unmarshal function of arguments +type Unmarshaler interface { + UnmarshalArgs(args []interface{}) error +} + +func unmarshalArgItems(params []json.RawMessage) ([]interface{}, error) { + args := make([]interface{}, len(params)) + for i, val := range params { + err := json.Unmarshal(val, &args[i]) + if err != nil { + return nil, err + } + } + return args, nil } diff --git a/btcjson/dashevocmds.go b/btcjson/dashevocmds.go index ad491e4815..698a974e44 100644 --- a/btcjson/dashevocmds.go +++ b/btcjson/dashevocmds.go @@ -7,6 +7,17 @@ package btcjson +import "errors" + +func init() { + // No special flags for commands in this file. + flags := UsageFlag(0) + + MustRegisterCmd("quorum", (*QuorumCmd)(nil), flags) + MustRegisterCmd("bls", (*BLSCmd)(nil), flags) + MustRegisterCmd("protx", (*ProTxCmd)(nil), flags) +} + type BLSSubCmd string const ( @@ -117,6 +128,11 @@ const ( LLMQType_5_60 LLMQType = 100 //24 blocks ) +var ( + errWrongSizeOfArgs = errors.New("wrong size of arguments") + errQuorumUnmarshalerNotFound = errors.New("quorum unmarshaler not found") +) + // QuorumCmd defines the quorum JSON-RPC command. type QuorumCmd struct { SubCmd QuorumCmdSubCmd `jsonrpcusage:"\"list|info|dkgstatus|sign|getrecsig|hasrecsig|isconflicting|memberof|selectquorum\""` @@ -422,11 +438,93 @@ func NewProTxRevokeCmd(proTxHash, operatorPrivateKey string, reason int, feeSour return r } -func init() { - // No special flags for commands in this file. - flags := UsageFlag(0) +// UnmarshalArgs maps a list of arguments to quorum struct +func (q *QuorumCmd) UnmarshalArgs(args []interface{}) error { + if len(args) == 0 { + return errWrongSizeOfArgs + } + subCmd := args[0].(string) + q.SubCmd = QuorumCmdSubCmd(subCmd) + unmarshaler, ok := quorumCmdUnmarshalers[string(q.SubCmd)] + if !ok { + return errQuorumUnmarshalerNotFound + } + return unmarshaler(q, args[1:]) +} - MustRegisterCmd("quorum", (*QuorumCmd)(nil), flags) - MustRegisterCmd("bls", (*BLSCmd)(nil), flags) - MustRegisterCmd("protx", (*ProTxCmd)(nil), flags) +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), +} + +func unmarshalLLMQType(val interface{}) (LLMQType, error) { + var vInt int + switch tv := val.(type) { + case float64: + vInt = int(tv) + case float32: + vInt = int(tv) + case int: + vInt = tv + case LLMQType: + return tv, nil + } + return LLMQType(vInt), nil +} + +func quorumInfoUnmarshaler(q *QuorumCmd, args []interface{}) error { + q.QuorumHash = strPtr(args[1].(string)) + q.IncludeSkShare = boolPtr(args[2].(bool)) + return nil +} + +func quorumSignUnmarshaler(q *QuorumCmd, args []interface{}) error { + q.RequestID = strPtr(args[1].(string)) + q.MessageHash = strPtr(args[2].(string)) + q.QuorumHash = strPtr(args[3].(string)) + q.Submit = boolPtr(args[4].(bool)) + return nil +} + +func unmarshalQuorumLLMQType(next unmarshalQuorumCmdFunc) unmarshalQuorumCmdFunc { + return func(q *QuorumCmd, args []interface{}) error { + val, err := unmarshalLLMQType(args[0]) + if err != nil { + return err + } + q.LLMQType = llmqTypePtr(val) + return next(q, args) + } +} + +func quorumVerifyUnmarshaler(q *QuorumCmd, args []interface{}) error { + q.RequestID = strPtr(args[1].(string)) + q.MessageHash = strPtr(args[2].(string)) + q.QuorumHash = strPtr(args[3].(string)) + q.Signature = strPtr(args[4].(string)) + return nil +} + +func validateQuorumArgs(n int) func(unmarshalQuorumCmdFunc) unmarshalQuorumCmdFunc { + return func(next unmarshalQuorumCmdFunc) unmarshalQuorumCmdFunc { + return func(q *QuorumCmd, args []interface{}) error { + if n > len(args) { + return errWrongSizeOfArgs + } + return next(q, args) + } + } +} + +func withQuorumUnmarshaler( + unmarshaler unmarshalQuorumCmdFunc, + fns ...func(unmarshalQuorumCmdFunc) unmarshalQuorumCmdFunc, +) unmarshalQuorumCmdFunc { + for _, fn := range fns { + unmarshaler = fn(unmarshaler) + } + return unmarshaler } diff --git a/btcjson/dashevocmds_test.go b/btcjson/dashevocmds_test.go index 86ef52e14e..495b1a986c 100644 --- a/btcjson/dashevocmds_test.go +++ b/btcjson/dashevocmds_test.go @@ -26,7 +26,7 @@ func pLLMQType(l btcjson.LLMQType) *btcjson.LLMQType { return &l } func TestDashEvoCmds(t *testing.T) { t.Parallel() - testID := int(1) + testID := 1 tests := []struct { name string newCmd func() (interface{}, error) @@ -37,7 +37,7 @@ func TestDashEvoCmds(t *testing.T) { { name: "quorum sign", newCmd: func() (interface{}, error) { - return btcjson.NewCmd("quorum sign", btcjson.LLMQType_100_67, + return btcjson.NewCmd("quorum", "sign", btcjson.LLMQType_100_67, "0067c4fd779a195a95b267e263c631f71f83f8d5e6191091289d114012b373a1", "ce490ca26cad6f1749ff9b977fe0fe4ece4391166f69be75c4619bc94b184dbc", "6f1018f54507606069303fd16257434073c6f374729b0090bb9dbbe629241236", @@ -50,8 +50,9 @@ func TestDashEvoCmds(t *testing.T) { "6f1018f54507606069303fd16257434073c6f374729b0090bb9dbbe629241236", false) }, - marshalled: `{"jsonrpc":"1.0","method":"quorum sign","params":[4,"0067c4fd779a195a95b267e263c631f71f83f8d5e6191091289d114012b373a1","ce490ca26cad6f1749ff9b977fe0fe4ece4391166f69be75c4619bc94b184dbc","6f1018f54507606069303fd16257434073c6f374729b0090bb9dbbe629241236",false],"id":1}`, + marshalled: `{"jsonrpc":"1.0","method":"quorum","params":["sign",4,"0067c4fd779a195a95b267e263c631f71f83f8d5e6191091289d114012b373a1","ce490ca26cad6f1749ff9b977fe0fe4ece4391166f69be75c4619bc94b184dbc","6f1018f54507606069303fd16257434073c6f374729b0090bb9dbbe629241236",false],"id":1}`, unmarshalled: &btcjson.QuorumCmd{ + SubCmd: "sign", LLMQType: pLLMQType(btcjson.LLMQType_100_67), RequestID: pString("0067c4fd779a195a95b267e263c631f71f83f8d5e6191091289d114012b373a1"), MessageHash: pString("ce490ca26cad6f1749ff9b977fe0fe4ece4391166f69be75c4619bc94b184dbc"), @@ -62,7 +63,7 @@ func TestDashEvoCmds(t *testing.T) { { name: "quorum info", newCmd: func() (interface{}, error) { - return btcjson.NewCmd("quorum info", btcjson.LLMQType_100_67, + return btcjson.NewCmd("quorum", "info", btcjson.LLMQType_100_67, "0067c4fd779a195a95b267e263c631f71f83f8d5e6191091289d114012b373a1", false) }, @@ -71,22 +72,24 @@ func TestDashEvoCmds(t *testing.T) { "0067c4fd779a195a95b267e263c631f71f83f8d5e6191091289d114012b373a1", false) }, - marshalled: `{"jsonrpc":"1.0","method":"quorum info","params":[4,"0067c4fd779a195a95b267e263c631f71f83f8d5e6191091289d114012b373a1",false],"id":1}`, + marshalled: `{"jsonrpc":"1.0","method":"quorum","params":["info",4,"0067c4fd779a195a95b267e263c631f71f83f8d5e6191091289d114012b373a1",false],"id":1}`, unmarshalled: &btcjson.QuorumCmd{ + SubCmd: "info", LLMQType: pLLMQType(btcjson.LLMQType_100_67), QuorumHash: pString("0067c4fd779a195a95b267e263c631f71f83f8d5e6191091289d114012b373a1"), IncludeSkShare: pBool(false), }, }, { - name: "quorum verify", + name: "quorum", newCmd: func() (interface{}, error) { - return btcjson.NewCmd("quorum verify", + return btcjson.NewCmd("quorum", + "verify", btcjson.LLMQType_100_67, "0067c4fd779a195a95b267e263c631f71f83f8d5e6191091289d114012b373a1", "ce490ca26cad6f1749ff9b977fe0fe4ece4391166f69be75c4619bc94b184dbc", "6f1018f54507606069303fd16257434073c6f374729b0090bb9dbbe629241236", - false) + "5f1018f54507606069303fd16257434073c6f374729b0090bb9dbbe629241235") }, staticCmd: func() interface{} { return btcjson.NewQuorumVerifyCmd(btcjson.LLMQType_100_67, @@ -95,8 +98,9 @@ func TestDashEvoCmds(t *testing.T) { "5f1018f54507606069303fd16257434073c6f374729b0090bb9dbbe629241235", "6f1018f54507606069303fd16257434073c6f374729b0090bb9dbbe629241236") }, - marshalled: `{"jsonrpc":"1.0","method":"quorum sign","params":[4,"0067c4fd779a195a95b267e263c631f71f83f8d5e6191091289d114012b373a1","ce490ca26cad6f1749ff9b977fe0fe4ece4391166f69be75c4619bc94b184dbc","5f1018f54507606069303fd16257434073c6f374729b0090bb9dbbe629241235","6f1018f54507606069303fd16257434073c6f374729b0090bb9dbbe629241236",],"id":1}`, + marshalled: `{"jsonrpc":"1.0","method":"quorum","params":["verify",4,"0067c4fd779a195a95b267e263c631f71f83f8d5e6191091289d114012b373a1","ce490ca26cad6f1749ff9b977fe0fe4ece4391166f69be75c4619bc94b184dbc","6f1018f54507606069303fd16257434073c6f374729b0090bb9dbbe629241236","5f1018f54507606069303fd16257434073c6f374729b0090bb9dbbe629241235"],"id":1}`, unmarshalled: &btcjson.QuorumCmd{ + SubCmd: "verify", LLMQType: pLLMQType(btcjson.LLMQType_100_67), RequestID: pString("0067c4fd779a195a95b267e263c631f71f83f8d5e6191091289d114012b373a1"), MessageHash: pString("ce490ca26cad6f1749ff9b977fe0fe4ece4391166f69be75c4619bc94b184dbc"), diff --git a/btcjson/util.go b/btcjson/util.go new file mode 100644 index 0000000000..62a4901f4f --- /dev/null +++ b/btcjson/util.go @@ -0,0 +1,13 @@ +package btcjson + +func strPtr(v string) *string { + return &v +} + +func boolPtr(v bool) *bool { + return &v +} + +func llmqTypePtr(v LLMQType) *LLMQType { + return &v +}