Skip to content

Commit bec3bd7

Browse files
committed
[KLC-1546] Operator multi-sign actions (#954)
* Add raw decode transaction and multisign API converter * Add log level control for ms-encode command * Add multisign API transaction encoding and add to pool * Add ms-broadcast command to broadcast transactions via multisign API * Add ms-fetch command to retrieve transaction details from multisign API * Add ms-by-address command to fetch transactions from multisign API by address * Move multisign operation do MS subcommand * Fix JSON tag syntax for Threshold field in MSApiTransaction struct * Simplify error handling in decode transaction command
1 parent cfccd40 commit bec3bd7

3 files changed

Lines changed: 266 additions & 4 deletions

File tree

cmd/operator/main.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,9 @@ var rootCmd = &cobra.Command{
9898
log.SetLevel(logger.LogTrace)
9999
}
100100

101-
if createOnly || strings.HasPrefix(cmd.Use, "sign") {
101+
if createOnly ||
102+
strings.HasPrefix(cmd.Use, "sign") ||
103+
strings.HasPrefix(cmd.Use, "encode") {
102104
log.SetLevel(logger.LogNone)
103105
}
104106

cmd/operator/multisign.go

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
package main
2+
3+
import (
4+
"encoding/hex"
5+
"encoding/json"
6+
"fmt"
7+
"strings"
8+
9+
"github.com/klever-io/klever-go/cmd/operator/utils"
10+
"github.com/klever-io/klever-go/data/transaction"
11+
"github.com/spf13/cobra"
12+
)
13+
14+
var (
15+
multisignAPI string
16+
)
17+
18+
func subMS() []*cobra.Command {
19+
20+
cmdDecodeTransaction := &cobra.Command{
21+
Use: "decode [Transaction]",
22+
Args: cobra.ExactArgs(1),
23+
Short: "decode a transaction",
24+
RunE: func(cmd *cobra.Command, args []string) error {
25+
txm := args[0]
26+
// try marshal data
27+
TX := &transaction.Transaction{}
28+
err := json.Unmarshal([]byte(txm), TX)
29+
if err != nil {
30+
return err
31+
}
32+
33+
return formatAndDumpRawTX(TX)
34+
},
35+
}
36+
37+
cmdMultisignEncodeTransaction := &cobra.Command{
38+
Use: "encode [Transaction]",
39+
Args: cobra.ExactArgs(1),
40+
Short: "encode a transaction form multisign API",
41+
RunE: func(cmd *cobra.Command, args []string) error {
42+
txm := args[0]
43+
// try marshal data
44+
tx := &transaction.Transaction{}
45+
err := json.Unmarshal([]byte(txm), tx)
46+
if err != nil {
47+
return err
48+
}
49+
50+
encoded, err := encodeMSApiData(tx)
51+
if err != nil {
52+
return err
53+
}
54+
55+
return DumpAsJson(encoded)
56+
},
57+
}
58+
59+
cmdMultisignAddTransaction := &cobra.Command{
60+
Use: "append [Transaction]",
61+
Args: cobra.ExactArgs(1),
62+
Short: "append transaction data into multisign API",
63+
RunE: func(cmd *cobra.Command, args []string) error {
64+
txm := args[0]
65+
66+
encoded := &MSApiEncoded{}
67+
// ty encode as raw data
68+
tx := &transaction.Transaction{}
69+
err := json.Unmarshal([]byte(txm), tx)
70+
if err != nil {
71+
// try to decode API data
72+
log.Info("decoding multisign API data")
73+
err = json.Unmarshal([]byte(txm), encoded)
74+
if err != nil {
75+
return err
76+
}
77+
} else {
78+
encoded, err = encodeMSApiData(tx)
79+
if err != nil {
80+
return err
81+
}
82+
}
83+
84+
// marshal
85+
data, err := json.Marshal(encoded)
86+
if err != nil {
87+
return err
88+
}
89+
90+
broadcastResult := struct {
91+
Status string `json:"status"`
92+
Error string `json:"error"`
93+
}{}
94+
95+
// broadcast
96+
log.Info("broadcasting...")
97+
err = utils.PostURL(fmt.Sprintf("%s/transaction", multisignAPI), string(data), nil, &broadcastResult)
98+
if err != nil {
99+
return err
100+
}
101+
if len(broadcastResult.Error) != 0 {
102+
return fmt.Errorf("error broadcasting transaction: %s", broadcastResult.Error)
103+
}
104+
log.Info("successful added", "txHash", encoded.Hash, "address", encoded.Address)
105+
106+
return nil
107+
},
108+
}
109+
110+
cmdMultisignBroadcast := &cobra.Command{
111+
Use: "broadcast [Transaction]",
112+
Args: cobra.ExactArgs(1),
113+
Short: "broadcast a transaction form multisign API",
114+
RunE: func(cmd *cobra.Command, args []string) error {
115+
hash := args[0]
116+
hash = strings.Replace(hash, "0x", "", 1)
117+
if len(hash) != 64 {
118+
return fmt.Errorf("invalid TX hash length: %d", len(hash))
119+
}
120+
_, err := hex.DecodeString(hash)
121+
if len(hash) != 64 || err != nil {
122+
return fmt.Errorf("invalid TX hash %s", hash)
123+
}
124+
125+
result := struct {
126+
Status string `json:"status"`
127+
Error string `json:"error"`
128+
}{}
129+
130+
err = utils.PostURL(fmt.Sprintf("%s/broadcast/%s", multisignAPI, hash), "", nil, &result)
131+
if err != nil {
132+
return err
133+
}
134+
if len(result.Error) != 0 {
135+
return fmt.Errorf("error broadcasting transaction: %s", result.Error)
136+
}
137+
log.Info("successful added", "txHash", hash)
138+
139+
return nil
140+
},
141+
}
142+
143+
cmdMultisignFetch := &cobra.Command{
144+
Use: "by-hash [Transaction]",
145+
Args: cobra.ExactArgs(1),
146+
Short: "fetch a transaction form multisign API",
147+
RunE: func(cmd *cobra.Command, args []string) error {
148+
hash := args[0]
149+
hash = strings.Replace(hash, "0x", "", 1)
150+
if len(hash) != 64 {
151+
return fmt.Errorf("invalid TX hash length: %d", len(hash))
152+
}
153+
_, err := hex.DecodeString(hash)
154+
if len(hash) != 64 || err != nil {
155+
return fmt.Errorf("invalid TX hash %s", hash)
156+
}
157+
158+
result := MSApiTransaction{}
159+
err = utils.GetURL(fmt.Sprintf("%s/transaction/%s", multisignAPI, hash), &result)
160+
if err != nil {
161+
return err
162+
}
163+
164+
return DumpAsJson(result)
165+
},
166+
}
167+
168+
cmdMultisignByAddress := &cobra.Command{
169+
Use: "by-address [Address]",
170+
Args: cobra.ExactArgs(1),
171+
Short: "fetch a transaction form multisign API",
172+
RunE: func(cmd *cobra.Command, args []string) error {
173+
address := args[0]
174+
175+
result := make([]MSApiTransaction, 0)
176+
err := utils.GetURL(fmt.Sprintf("%s/transaction/by-address/%s", multisignAPI, address), &result)
177+
if err != nil {
178+
return err
179+
}
180+
181+
return DumpAsJson(result)
182+
},
183+
}
184+
185+
return []*cobra.Command{
186+
cmdDecodeTransaction,
187+
cmdMultisignEncodeTransaction,
188+
cmdMultisignAddTransaction,
189+
cmdMultisignBroadcast,
190+
cmdMultisignFetch,
191+
cmdMultisignByAddress,
192+
}
193+
}
194+
195+
func init() {
196+
cmdMS := &cobra.Command{
197+
Use: "ms",
198+
Short: "multisign actions",
199+
RunE: func(cmd *cobra.Command, args []string) error {
200+
return cmd.Help()
201+
},
202+
}
203+
cmdMS.AddCommand(subMS()...)
204+
205+
cmdMS.Flags().StringVar(&multisignAPI, "multisign-api", "https://multisign.mainnet.klever.org", "multisign API URL")
206+
207+
rootCmd.AddCommand(cmdMS)
208+
}

cmd/operator/multsignHelper.go

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,44 @@ var (
2222
fbToNode string
2323
)
2424

25+
type MSApiEncoded struct {
26+
Hash string
27+
Address string
28+
Raw *transaction.Transaction
29+
}
30+
31+
type MSApiTransaction struct {
32+
Hash string `json:"hash"`
33+
Address string `json:"address"`
34+
Signers []struct {
35+
Address string `json:"address"`
36+
Weight int64 `json:"weight"`
37+
Signed bool `json:"signed"`
38+
} `json:"signers"`
39+
Threshold int64 `json:"threshold"`
40+
41+
Raw *transaction.Transaction `json:"raw"`
42+
}
43+
44+
func encodeMSApiData(tx *transaction.Transaction) (*MSApiEncoded, error) {
45+
// compute hash
46+
hash, err := computeTxHash(tx)
47+
if err != nil {
48+
return nil, err
49+
}
50+
51+
// get sender address encoded
52+
sender := walletPubKeyConverter.Encode(tx.RawData.Sender)
53+
54+
encoded := &MSApiEncoded{
55+
Hash: hex.EncodeToString(hash),
56+
Address: sender,
57+
Raw: tx,
58+
}
59+
60+
return encoded, nil
61+
}
62+
2563
func init() {
2664
cmdSign := &cobra.Command{
2765
Use: "sign [Transaction]",
@@ -110,7 +148,11 @@ func init() {
110148
}
111149
cmdForceBroadcast.Flags().StringVar(&fbToNode, "fb-node", "", "forwarding to")
112150

113-
rootCmd.AddCommand(cmdSign, cmdBroadcast, cmdForceBroadcast)
151+
rootCmd.AddCommand(
152+
cmdSign,
153+
cmdBroadcast,
154+
cmdForceBroadcast,
155+
)
114156
}
115157

116158
func broadcastHash(hash string) error {
@@ -222,7 +264,7 @@ func DumpAsJson(data interface{}) error {
222264
return nil
223265
}
224266

225-
func SignTX(TX *transaction.Transaction) ([]byte, error) {
267+
func computeTxHash(tx *transaction.Transaction) ([]byte, error) {
226268
// hash TX and verify
227269
hasher, err := factoryHasher.NewHasher("blake2b")
228270
if err != nil {
@@ -234,7 +276,17 @@ func SignTX(TX *transaction.Transaction) ([]byte, error) {
234276
return nil, err
235277
}
236278

237-
hash, err := tools.CalculateHash(internalMarshalizer, hasher, TX.RawData)
279+
hash, err := tools.CalculateHash(internalMarshalizer, hasher, tx.RawData)
280+
if err != nil {
281+
return nil, err
282+
}
283+
284+
return hash, nil
285+
}
286+
287+
func SignTX(TX *transaction.Transaction) ([]byte, error) {
288+
// hash TX and verify
289+
hash, err := computeTxHash(TX)
238290
if err != nil {
239291
return nil, err
240292
}

0 commit comments

Comments
 (0)