OSDN Git Service

Dev (#148)
[bytom/bytom.git] / cmd / bytomcli / main.go
1 package main
2
3 import (
4         "bytes"
5         "context"
6         "encoding/hex"
7         stdjson "encoding/json"
8         "flag"
9         "fmt"
10         "io"
11         "net"
12         "net/http"
13         "os"
14         "path/filepath"
15         "strconv"
16         "strings"
17         "time"
18
19         "github.com/bytom/blockchain"
20         "github.com/bytom/blockchain/query"
21         "github.com/bytom/blockchain/rpc"
22         "github.com/bytom/blockchain/txbuilder"
23         "github.com/bytom/cmd/bytomcli/example"
24         "github.com/bytom/crypto/ed25519"
25         "github.com/bytom/crypto/ed25519/chainkd"
26         "github.com/bytom/encoding/json"
27         "github.com/bytom/env"
28         "github.com/bytom/errors"
29         "github.com/bytom/config"
30 )
31
32 // config vars
33 var (
34         home    = blockchain.HomeDirFromEnvironment()
35         coreURL = env.String("BYTOM_URL", "http://localhost:1999")
36
37         // build vars; initialized by the linker
38         buildTag    = "?"
39         buildCommit = "?"
40         buildDate   = "?"
41 )
42
43 type command struct {
44         f func(*rpc.Client, []string)
45 }
46
47 type grantReq struct {
48         Policy    string      `json:"policy"`
49         GuardType string      `json:"guard_type"`
50         GuardData interface{} `json:"guard_data"`
51 }
52
53 var commands = map[string]*command{
54         "create-block-keypair":     {createBlockKeyPair},
55         "reset":                    {reset},
56         "grant":                    {grant},
57         "revoke":                   {revoke},
58         "wait":                     {wait},
59         "create-account":           {createAccount},
60         "bind-account":             {bindAccount},
61         "update-account-tags":      {updateAccountTags},
62         "create-asset":             {createAsset},
63         "bind-asset":               {bindAsset},
64         "update-asset-tags":        {updateAssetTags},
65         "build-transaction":        {buildTransaction},
66         "create-control-program":   {createControlProgram},
67         "create-account-receiver":  {createAccountReceiver},
68         "create-transaction-feed":  {createTxFeed},
69         "get-transaction-feed":     {getTxFeed},
70         "update-transaction-feed":  {updateTxFeed},
71         "list-accounts":            {listAccounts},
72         "list-assets":              {listAssets},
73         "list-transaction-feeds":   {listTxFeeds},
74         "list-transactions":        {listTransactions},
75         "list-balances":            {listBalances},
76         "list-unspent-outputs":     {listUnspentOutputs},
77         "delete-transaction-feed":  {deleteTxFeed},
78         "issue-test":               {example.IssueTest},
79         "spend-test":               {example.SpendTest},
80         "spend-coinbase-test":      {example.CoinbaseTest},
81         "wallet-test":              {example.WalletTest},
82         "create-access-token":      {createAccessToken},
83         "list-access-token":        {listAccessTokens},
84         "delete-access-token":      {deleteAccessToken},
85         "check-access-token":       {checkAccessToken},
86         "create-key":               {createKey},
87         "list-keys":                {listKeys},
88         "delete-key":               {deleteKey},
89         "sign-transactions":        {signTransactions},
90         "sub-create-issue-tx":      {submitCreateIssueTransaction},
91         "sub-spend-account-tx":     {submitSpendTransaction},
92         "sub-control-receiver-tx":  {submitReceiverTransaction},
93         "reset-password":           {resetPassword},
94         "update-alias":             {updateAlias},
95         "net-info":                 {netInfo},
96         "get-best-block-hash":      {getBestBlockHash},
97         "get-block-header-by-hash": {getBlockHeaderByHash},
98         "get-block-by-hash":        {getBlockByHash},
99 }
100
101 func main() {
102         env.Parse()
103
104         if len(os.Args) >= 2 && os.Args[1] == "-version" {
105                 var version string
106                 if buildTag != "?" {
107                         // build tag with bytom- prefix indicates official release
108                         version = strings.TrimPrefix(buildTag, "bytom-")
109                 } else {
110                         // version of the form rev123 indicates non-release build
111                         //version = rev.ID
112                 }
113                 fmt.Printf("bytomcli %s\n", version)
114                 return
115         }
116
117         if len(os.Args) < 2 {
118                 help(os.Stdout)
119                 os.Exit(0)
120         }
121         cmd := commands[os.Args[1]]
122         if cmd == nil {
123                 fmt.Fprintln(os.Stderr, "unknown command:", os.Args[1])
124                 help(os.Stderr)
125                 os.Exit(1)
126         }
127         cmd.f(mustRPCClient(), os.Args[2:])
128 }
129
130 func createBlockKeyPair(client *rpc.Client, args []string) {
131         if len(args) != 0 {
132                 fatalln("error: create-block-keypair takes no args")
133         }
134         pub := struct {
135                 Pub ed25519.PublicKey
136         }{}
137         err := client.Call(context.Background(), "/mockhsm/create-block-key", nil, &pub)
138         dieOnRPCError(err)
139         fmt.Printf("%x\n", pub.Pub)
140 }
141
142 // reset will attempt a reset rpc call on a remote core. If the
143 // core is not configured with reset capabilities an error is returned.
144 func reset(client *rpc.Client, args []string) {
145         if len(args) != 0 {
146                 fatalln("error: reset takes no args")
147         }
148
149         req := map[string]bool{
150                 "Everything": true,
151         }
152
153         err := client.Call(context.Background(), "/reset", req, nil)
154         dieOnRPCError(err)
155 }
156
157 func grant(client *rpc.Client, args []string) {
158         editAuthz(client, args, "grant")
159 }
160
161 func revoke(client *rpc.Client, args []string) {
162         editAuthz(client, args, "revoke")
163 }
164
165 func editAuthz(client *rpc.Client, args []string, action string) {
166         usage := "usage: corectl " + action + " [policy] [guard]"
167         var flags flag.FlagSet
168
169         flags.Usage = func() {
170                 fmt.Fprintln(os.Stderr, usage)
171                 fmt.Fprintln(os.Stderr, `
172 Where guard is one of:
173   token=[id]   to affect an access token
174   CN=[name]    to affect an X.509 Common Name
175   OU=[name]    to affect an X.509 Organizational Unit
176
177 The type of guard (before the = sign) is case-insensitive.
178 `)
179                 os.Exit(1)
180         }
181         flags.Parse(args)
182         args = flags.Args()
183         if len(args) != 2 {
184                 fatalln(usage)
185         }
186
187         req := grantReq{Policy: args[0]}
188
189         switch typ, data := splitAfter2(args[1], "="); strings.ToUpper(typ) {
190         case "TOKEN=":
191                 req.GuardType = "access_token"
192                 req.GuardData = map[string]interface{}{"id": data}
193         case "CN=":
194                 req.GuardType = "x509"
195                 req.GuardData = map[string]interface{}{"subject": map[string]string{"CN": data}}
196         case "OU=":
197                 req.GuardType = "x509"
198                 req.GuardData = map[string]interface{}{"subject": map[string]string{"OU": data}}
199         default:
200                 fmt.Fprintln(os.Stderr, "unknown guard type", typ)
201                 fatalln(usage)
202         }
203
204         path := map[string]string{
205                 "grant":  "/create-authorization-grant",
206                 "revoke": "/delete-authorization-grant",
207         }[action]
208         err := client.Call(context.Background(), path, req, nil)
209         dieOnRPCError(err)
210 }
211
212 func mustRPCClient() *rpc.Client {
213         // TODO(kr): refactor some of this cert-loading logic into bytom/blockchain
214         // and use it from cored as well.
215         // Note that this function, unlike maybeUseTLS in cored,
216         // does not load the cert and key from env vars,
217         // only from the filesystem.
218         certFile := filepath.Join(home, "tls.crt")
219         keyFile := filepath.Join(home, "tls.key")
220         config, err := blockchain.TLSConfig(certFile, keyFile, "")
221         if err == blockchain.ErrNoTLS {
222                 return &rpc.Client{BaseURL: *coreURL}
223         } else if err != nil {
224                 fatalln("error: loading TLS cert:", err)
225         }
226
227         t := &http.Transport{
228                 DialContext: (&net.Dialer{
229                         Timeout:   30 * time.Second,
230                         KeepAlive: 30 * time.Second,
231                         DualStack: true,
232                 }).DialContext,
233                 MaxIdleConns:          100,
234                 IdleConnTimeout:       90 * time.Second,
235                 TLSClientConfig:       config,
236                 TLSHandshakeTimeout:   10 * time.Second,
237                 ExpectContinueTimeout: 1 * time.Second,
238         }
239
240         url := *coreURL
241         if strings.HasPrefix(url, "http:") {
242                 url = "https:" + url[5:]
243         }
244
245         return &rpc.Client{
246                 BaseURL: url,
247                 Client:  &http.Client{Transport: t},
248         }
249 }
250
251 func fatalln(v ...interface{}) {
252         fmt.Printf("%v", v)
253         os.Exit(2)
254 }
255
256 func dieOnRPCError(err error, prefixes ...interface{}) {
257         if err == nil {
258                 return
259         }
260
261         if len(prefixes) > 0 {
262                 fmt.Fprintln(os.Stderr, prefixes...)
263         }
264
265         if msgErr, ok := errors.Root(err).(rpc.ErrStatusCode); ok && msgErr.ErrorData != nil {
266                 fmt.Fprintln(os.Stderr, "RPC error:", msgErr.ErrorData.ChainCode, msgErr.ErrorData.Message)
267                 if msgErr.ErrorData.Detail != "" {
268                         fmt.Fprintln(os.Stderr, "Detail:", msgErr.ErrorData.Detail)
269                 }
270         } else {
271                 fmt.Fprintln(os.Stderr, "RPC error:", err)
272         }
273
274         os.Exit(2)
275 }
276
277 func help(w io.Writer) {
278         fmt.Fprintln(w, "usage: corectl [-version] [command] [arguments]")
279         fmt.Fprint(w, "\nThe commands are:\n\n")
280         for name := range commands {
281                 fmt.Fprintln(w, "\t", name)
282         }
283         fmt.Fprint(w, "\nFlags:\n")
284         fmt.Fprintln(w, "\t-version   print version information")
285         fmt.Fprintln(w)
286 }
287
288 // splitAfter2 is like strings.SplitAfterN with n=2.
289 // If sep is not in s, it returns a="" and b=s.
290 func splitAfter2(s, sep string) (a, b string) {
291         i := strings.Index(s, sep)
292         k := i + len(sep)
293         return s[:k], s[k:]
294 }
295
296 func wait(client *rpc.Client, args []string) {
297         if len(args) != 0 {
298                 fatalln("error: wait takes no args")
299         }
300
301         for {
302                 err := client.Call(context.Background(), "/info", nil, nil)
303                 if err == nil {
304                         break
305                 }
306
307                 if statusErr, ok := errors.Root(err).(rpc.ErrStatusCode); ok && statusErr.StatusCode/100 != 5 {
308                         break
309                 }
310
311                 time.Sleep(500 * time.Millisecond)
312         }
313 }
314
315 func createAccount(client *rpc.Client, args []string) {
316         if len(args) != 1 {
317                 fatalln("error: createAccount takes no args")
318         }
319         xprv, err := chainkd.NewXPrv(nil)
320         if err != nil {
321                 fatalln("NewXprv error.")
322         }
323         xpub := xprv.XPub()
324         fmt.Printf("xprv:%v\n", xprv)
325         fmt.Printf("xpub:%v\n", xpub)
326         type Ins struct {
327                 RootXPubs   []chainkd.XPub `json:"root_xpubs"`
328                 Quorum      int
329                 Alias       string
330                 Tags        map[string]interface{}
331                 ClientToken string `json:"client_token"`
332         }
333         var ins Ins
334         ins.RootXPubs = []chainkd.XPub{xpub}
335         ins.Quorum = 1
336         ins.Alias = args[0]
337         ins.Tags = map[string]interface{}{"test_tag": "v0"}
338         ins.ClientToken = args[0]
339         account := make([]query.AnnotatedAccount, 1)
340         client.Call(context.Background(), "/create-account", &[]Ins{ins}, &account)
341         fmt.Printf("responses:%v\n", account[0])
342         fmt.Printf("account id:%v\n", account[0].ID)
343 }
344
345 func bindAccount(client *rpc.Client, args []string) {
346         if len(args) != 2 {
347                 fatalln("error: bindAccount need args [account alias name] [account pub key]")
348         }
349         var xpub chainkd.XPub
350         err := xpub.UnmarshalText([]byte(args[1]))
351         if err == nil {
352                 fmt.Printf("xpub:%v\n", xpub)
353         } else {
354                 fmt.Printf("xpub unmarshal error:%v\n", xpub)
355         }
356         type Ins struct {
357                 RootXPubs   []chainkd.XPub `json:"root_xpubs"`
358                 Quorum      int
359                 Alias       string
360                 Tags        map[string]interface{}
361                 ClientToken string `json:"client_token"`
362         }
363         var ins Ins
364         ins.RootXPubs = []chainkd.XPub{xpub}
365         ins.Quorum = 1
366         ins.Alias = args[0]
367         ins.Tags = map[string]interface{}{"test_tag": "v0"}
368         ins.ClientToken = args[0]
369         account := make([]query.AnnotatedAccount, 1)
370         client.Call(context.Background(), "/create-account", &[]Ins{ins}, &account)
371         fmt.Printf("responses:%v\n", account[0])
372         fmt.Printf("account id:%v\n", account[0].ID)
373 }
374
375 func createAsset(client *rpc.Client, args []string) {
376         if len(args) != 1 {
377                 fatalln("error: createAsset takes no args")
378         }
379         xprv, err := chainkd.NewXPrv(nil)
380         if err != nil {
381                 fatalln("NewXprv error.")
382         }
383         xprv_, _ := xprv.MarshalText()
384         xpub := xprv.XPub()
385         fmt.Printf("xprv:%v\n", string(xprv_))
386         xpub_, _ := xpub.MarshalText()
387         fmt.Printf("xpub:%v\n", xpub_)
388         type Ins struct {
389                 RootXPubs   []chainkd.XPub `json:"root_xpubs"`
390                 Quorum      int
391                 Alias       string
392                 Tags        map[string]interface{}
393                 Definition  map[string]interface{}
394                 ClientToken string `json:"client_token"`
395         }
396         var ins Ins
397         ins.RootXPubs = []chainkd.XPub{xpub}
398         ins.Quorum = 1
399         ins.Alias = args[0]
400         ins.Tags = map[string]interface{}{"test_tag": "v0"}
401         ins.Definition = map[string]interface{}{}
402         ins.ClientToken = args[0]
403         assets := make([]query.AnnotatedAsset, 1)
404         client.Call(context.Background(), "/create-asset", &[]Ins{ins}, &assets)
405         fmt.Printf("responses:%v\n", assets)
406         fmt.Printf("asset id:%v\n", assets[0].ID.String())
407 }
408
409 func bindAsset(client *rpc.Client, args []string) {
410         if len(args) != 2 {
411                 fatalln("error: bindAsset need args [asset name] [asset xpub]")
412         }
413         var xpub chainkd.XPub
414         err := xpub.UnmarshalText([]byte(args[1]))
415         if err == nil {
416                 fmt.Printf("xpub:%v\n", xpub)
417         } else {
418                 fmt.Printf("xpub unmarshal error:%v\n", xpub)
419         }
420         type Ins struct {
421                 RootXPubs   []chainkd.XPub `json:"root_xpubs"`
422                 Quorum      int
423                 Alias       string
424                 Tags        map[string]interface{}
425                 Definition  map[string]interface{}
426                 ClientToken string `json:"client_token"`
427         }
428         var ins Ins
429         ins.RootXPubs = []chainkd.XPub{xpub}
430         ins.Quorum = 1
431         ins.Alias = args[0]
432         ins.Tags = map[string]interface{}{"test_tag": "v0"}
433         ins.Definition = map[string]interface{}{}
434         ins.ClientToken = args[0]
435         assets := make([]query.AnnotatedAsset, 1)
436         client.Call(context.Background(), "/create-asset", &[]Ins{ins}, &assets)
437         //dieOnRPCError(err)
438         fmt.Printf("responses:%v\n", assets)
439         fmt.Printf("asset id:%v\n", assets[0].ID.String())
440 }
441
442 func updateAccountTags(client *rpc.Client, args []string) {
443         if len(args) != 2 {
444                 fatalln("update-account-tags [<ID>|<alias>] [tags_key:<tags_value>]")
445         }
446
447         type Ins struct {
448                 ID    *string
449                 Alias *string
450                 Tags  map[string]interface{} `json:"tags"`
451         }
452         var ins Ins
453
454         //TODO:(1)when alias = acc...,how to do;
455         //TODO:(2)support more tags together
456         if "acc" == args[0][:3] {
457                 ins.ID = &args[0]
458                 ins.Alias = nil
459         } else {
460                 ins.Alias = &args[0]
461                 ins.ID = nil
462         }
463
464         tags := strings.Split(args[1], ":")
465         if len(tags) != 2 {
466                 fatalln("update-account-tags [<ID>|<alias>] [tags_key:<tags_value>]")
467         }
468
469         ins.Tags = map[string]interface{}{tags[0]: tags[1]}
470         responses := make([]interface{}, 50)
471         client.Call(context.Background(), "/update-account-tags", &[]Ins{ins}, &responses)
472         fmt.Printf("responses:%v\n", responses)
473 }
474
475 func updateAssetTags(client *rpc.Client, args []string) {
476         if len(args) != 0 {
477                 fatalln("error:updateAccountTags not use args")
478         }
479         type Ins struct {
480                 ID    *string
481                 Alias *string
482                 Tags  map[string]interface{} `json:"tags"`
483         }
484         var ins Ins
485         id := "123456"
486         alias := "asdfg"
487         ins.ID = &id
488         ins.Alias = &alias
489         ins.Tags = map[string]interface{}{"test_tag": "v0"}
490         responses := make([]interface{}, 50)
491         client.Call(context.Background(), "/update-asset-tags", &[]Ins{ins}, &responses)
492         fmt.Printf("responses:%v\n", responses)
493 }
494
495 func buildTransaction(client *rpc.Client, args []string) {
496         if len(args) != 3 {
497                 fatalln("error: need args: [account id] [asset id] [file name]")
498         }
499         // Build Transaction.
500         fmt.Printf("To build transaction:\n")
501         // Now Issue actions
502         buildReqFmt := `
503                 {"actions": [
504                         {
505                                 "type":"spend_account_unspent_output",
506                                 "receiver":null,
507                                 "output_id":"%v",
508                                 "reference_data":{}
509                         },
510                         {"type": "issue", "asset_id": "%s", "amount": 100},
511                         {"type": "control_account", "asset_id": "%s", "amount": 100, "account_id": "%s"}
512                 ]}`
513         buildReqStr := fmt.Sprintf(buildReqFmt, config.GenerateGenesisTx().ResultIds[0], args[1], args[1], args[0])
514         var buildReq blockchain.BuildRequest
515         err := stdjson.Unmarshal([]byte(buildReqStr), &buildReq)
516         if err != nil {
517                 fmt.Printf("json Unmarshal error.")
518         }
519
520         tpl := make([]txbuilder.Template, 1)
521         client.Call(context.Background(), "/build-transaction", []*blockchain.BuildRequest{&buildReq}, &tpl)
522         marshalTpl, _ := stdjson.Marshal(tpl[0])
523         fmt.Printf("tpl:%v\n", string(marshalTpl))
524         file, _ := os.Create(args[2])
525         defer file.Close()
526         file.Write(marshalTpl)
527 }
528
529 func submitCreateIssueTransaction(client *rpc.Client, args []string) {
530         if len(args) != 5 {
531                 fmt.Println("error: need args: [account id] [asset id] [issue amount] [asset xprv] [account xprv]")
532                 return
533         }
534         // Build Transaction.
535         fmt.Printf("To build transaction:\n")
536         // Now Issue actions
537         buildReqFmt := `
538                 {"actions": [
539                         {"type": "spend_account", "asset_id": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "amount":20000000, "account_id": "%s"},
540                         {"type": "issue", "asset_id": "%s", "amount": %s},
541                         {"type": "control_account", "asset_id": "%s", "amount": %s, "account_id": "%s"}
542                 ]}`
543         buildReqStr := fmt.Sprintf(buildReqFmt, args[0], args[1], args[2], args[1], args[2], args[0])
544         var buildReq blockchain.BuildRequest
545         err := stdjson.Unmarshal([]byte(buildReqStr), &buildReq)
546         if err != nil {
547                 fmt.Printf("json Unmarshal error.")
548                 os.Exit(1)
549         }
550
551         tpl := make([]txbuilder.Template, 1)
552         client.Call(context.Background(), "/build-transaction", []*blockchain.BuildRequest{&buildReq}, &tpl)
553         fmt.Printf("-----------tpl:%v\n", tpl[0])
554         fmt.Printf("----------tpl transaction:%v\n", tpl[0].Transaction)
555         fmt.Printf("----------btm inputs:%v\n", tpl[0].Transaction.Inputs[0])
556         fmt.Printf("----------issue inputs:%v\n", tpl[0].Transaction.Inputs[1])
557
558         mockWallet := make(map[chainkd.XPub]chainkd.XPrv)
559         var xprvAsset chainkd.XPrv
560         if err := xprvAsset.UnmarshalText([]byte(args[3])); err != nil {
561                 fmt.Printf("xprv unmarshal error:%v\n", xprvAsset)
562                 os.Exit(1)
563         }
564         mockWallet[xprvAsset.XPub()] = xprvAsset
565
566         var xprvAccount chainkd.XPrv
567         if err := xprvAccount.UnmarshalText([]byte(args[4])); err != nil {
568                 fmt.Printf("xprv unmarshal error:%v\n", xprvAccount)
569                 os.Exit(1)
570         }
571         mockWallet[xprvAccount.XPub()] = xprvAccount
572
573         // sign-transaction
574         err = txbuilder.Sign(context.Background(), &tpl[0], []chainkd.XPub{xprvAccount.XPub(), xprvAsset.XPub()}, "", func(_ context.Context, pub chainkd.XPub, path [][]byte, data [32]byte, _ string) ([]byte, error) {
575                 prv, ok := mockWallet[pub]
576                 if !ok {
577                         fmt.Println("fail to get mockWallet pubkey")
578                         os.Exit(1)
579                 }
580                 derived := prv.Derive(path)
581                 return derived.Sign(data[:]), nil
582         })
583         if err != nil {
584                 fmt.Printf("sign-transaction error. err:%v\n", err)
585         }
586         fmt.Printf("sign tpl:%v\n", tpl[0])
587         fmt.Printf("sign tpl's SigningInstructions:%v\n", tpl[0].SigningInstructions[0])
588         fmt.Printf("SigningInstructions's SignatureWitnesses:%v\n", tpl[0].SigningInstructions[0].SignatureWitnesses[0])
589
590         // submit-transaction
591         var submitResponse interface{}
592         submitArg := blockchain.SubmitArg{tpl, json.Duration{time.Duration(1000000)}, "none"}
593         client.Call(context.Background(), "/submit-transaction", submitArg, &submitResponse)
594         fmt.Printf("submit transaction:%v\n", submitResponse)
595 }
596
597 func submitReceiverTransaction(client *rpc.Client, args []string) {
598         if len(args) != 5 {
599                 fmt.Println("error: need args: [account xprv] [account id] [asset id] [spend amount] [control_program]")
600                 return
601         }
602
603         var xprvAccount chainkd.XPrv
604
605         err := xprvAccount.UnmarshalText([]byte(args[0]))
606         if err == nil {
607                 fmt.Printf("xprv:%v\n", xprvAccount)
608         } else {
609                 fmt.Printf("xprv unmarshal error:%v\n", xprvAccount)
610                 os.Exit(1)
611         }
612         // Build Transaction-Spend_account
613         fmt.Printf("To build transaction:\n")
614         buildReqFmt := `
615                 {"actions": [
616                     {"type": "spend_account", "asset_id": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "amount":20000000, "account_id": "%s"},
617                         {"type": "spend_account", "asset_id": "%s","amount": %s,"account_id": "%s"},
618                         {"type": "control_receiver", "asset_id": "%s", "amount": %s, "receiver":{"control_program": "%s","expires_at":"2017-12-28T12:52:06.78309768+08:00"}}
619         ]}`
620
621         buildReqStr := fmt.Sprintf(buildReqFmt, args[1], args[2], args[3], args[1], args[2], args[3], args[4])
622
623         var buildReq blockchain.BuildRequest
624         err = stdjson.Unmarshal([]byte(buildReqStr), &buildReq)
625         if err != nil {
626                 fmt.Println(err)
627                 os.Exit(1)
628         }
629
630         tpl := make([]txbuilder.Template, 1)
631         client.Call(context.Background(), "/build-transaction", []*blockchain.BuildRequest{&buildReq}, &tpl)
632         fmt.Printf("tpl:%v\n", tpl)
633
634         // sign-transaction-Spend_account
635         err = txbuilder.Sign(context.Background(), &tpl[0], []chainkd.XPub{xprvAccount.XPub()}, "", func(_ context.Context, _ chainkd.XPub, path [][]byte, data [32]byte, _ string) ([]byte, error) {
636                 derived := xprvAccount.Derive(path)
637                 return derived.Sign(data[:]), nil
638         })
639         if err != nil {
640                 fmt.Printf("sign-transaction error. err:%v\n", err)
641                 os.Exit(1)
642         }
643
644         fmt.Printf("sign tpl:%v\n", tpl[0])
645
646         // submit-transaction-Spend_account
647         var submitResponse interface{}
648         submitArg := blockchain.SubmitArg{Transactions: tpl, Wait: json.Duration{Duration: time.Duration(1000000)}, WaitUntil: "none"}
649         client.Call(context.Background(), "/submit-transaction", submitArg, &submitResponse)
650         fmt.Printf("submit transaction:%v\n", submitResponse)
651 }
652
653 func submitSpendTransaction(client *rpc.Client, args []string) {
654         if len(args) != 5 {
655                 fmt.Println("error: need args: [account1 id] [account2 id] [asset id] [account1 xprv] [spend amount]")
656                 return
657         }
658
659         var xprvAccount1 chainkd.XPrv
660
661         err := xprvAccount1.UnmarshalText([]byte(args[3]))
662         if err == nil {
663                 fmt.Printf("xprv:%v\n", xprvAccount1)
664         } else {
665                 fmt.Printf("xprv unmarshal error:%v\n", xprvAccount1)
666                 os.Exit(1)
667         }
668         // Build Transaction-Spend_account
669         fmt.Printf("To build transaction:\n")
670         buildReqFmt := `
671                 {"actions": [
672                     {"type": "spend_account", "asset_id": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "amount":20000000, "account_id": "%s"},
673                         {"type": "spend_account", "asset_id": "%s", "amount": %s, "account_id": "%s"},
674                         {"type": "control_account", "asset_id": "%s", "amount": %s, "account_id": "%s"}
675         ]}`
676
677         buildReqStr := fmt.Sprintf(buildReqFmt, args[0], args[2], args[4], args[0], args[2], args[4], args[1])
678
679         var buildReq blockchain.BuildRequest
680         err = stdjson.Unmarshal([]byte(buildReqStr), &buildReq)
681         if err != nil {
682                 fmt.Println(err)
683                 os.Exit(1)
684         }
685
686         tpl := make([]txbuilder.Template, 1)
687         client.Call(context.Background(), "/build-transaction", []*blockchain.BuildRequest{&buildReq}, &tpl)
688         fmt.Printf("tpl:%v\n", tpl)
689
690         // sign-transaction-Spend_account
691         err = txbuilder.Sign(context.Background(), &tpl[0], []chainkd.XPub{xprvAccount1.XPub()}, "", func(_ context.Context, _ chainkd.XPub, path [][]byte, data [32]byte, _ string) ([]byte, error) {
692                 derived := xprvAccount1.Derive(path)
693                 return derived.Sign(data[:]), nil
694         })
695         if err != nil {
696                 fmt.Printf("sign-transaction error. err:%v\n", err)
697                 os.Exit(1)
698         }
699
700         fmt.Printf("sign tpl:%v\n", tpl[0])
701
702         // submit-transaction-Spend_account
703         var submitResponse interface{}
704         submitArg := blockchain.SubmitArg{Transactions: tpl, Wait: json.Duration{Duration: time.Duration(1000000)}, WaitUntil: "none"}
705         client.Call(context.Background(), "/submit-transaction", submitArg, &submitResponse)
706         fmt.Printf("submit transaction:%v\n", submitResponse)
707 }
708
709 func createControlProgram(client *rpc.Client, args []string) {
710         if len(args) != 0 {
711                 fatalln("error:createControlProgram not use args")
712         }
713         type Ins struct {
714                 Type   string
715                 Params stdjson.RawMessage
716         }
717         var ins Ins
718         //TODO:undefined arguments to ins
719         responses := make([]interface{}, 50)
720         client.Call(context.Background(), "/create-control-program", &[]Ins{ins}, &responses)
721         fmt.Printf("responses:%v\n", responses)
722 }
723
724 func createAccountReceiver(client *rpc.Client, args []string) {
725         if len(args) != 1 {
726                 fmt.Println("error: need args: [account id] or [account alias]")
727                 return
728         }
729         type Ins struct {
730                 AccountInfo string    `json:"account_info"`
731                 ExpiresAt   time.Time `json:"expires_at"`
732         }
733         var ins Ins
734         var response interface{}
735
736         //TODO:undefined argument to ExpiresAt
737
738         ins.AccountInfo = args[0]
739
740         client.Call(context.Background(), "/create-account-receiver", &ins, &response)
741         fmt.Printf("responses:%v\n", response)
742 }
743
744 func createTxFeed(client *rpc.Client, args []string) {
745         if len(args) != 2 {
746                 fatalln("error:createTxFeed need arguments")
747         }
748         type In struct {
749                 Alias  string
750                 Filter string
751         }
752         var in In
753         in.Alias = args[0]
754         in.Filter = args[1]
755
756         client.Call(context.Background(), "/create-transaction-feed", in, nil)
757 }
758
759 func getTxFeed(client *rpc.Client, args []string) {
760         if len(args) != 1 {
761                 fatalln("error:getTxFeed use args alias")
762         }
763         type requestQuery struct {
764                 Filter       string        `json:"filter,omitempty"`
765                 FilterParams []interface{} `json:"filter_params,omitempty"`
766                 SumBy        []string      `json:"sum_by,omitempty"`
767                 PageSize     int           `json:"page_size"`
768                 AscLongPoll  bool          `json:"ascending_with_long_poll,omitempty"`
769                 Timeout      json.Duration `json:"timeout"`
770                 After        string        `json:"after"`
771                 StartTimeMS  uint64        `json:"start_time,omitempty"`
772                 EndTimeMS    uint64        `json:"end_time,omitempty"`
773                 TimestampMS  uint64        `json:"timestamp,omitempty"`
774                 Type         string        `json:"type"`
775                 Aliases      []string      `json:"aliases,omitempty"`
776         }
777         var in requestQuery
778         in.Filter = args[0]
779         responses := make([]interface{}, 0)
780         client.Call(context.Background(), "/get-transaction-feed", in, &responses)
781         if len(responses) > 0 {
782                 for i, item := range responses {
783                         fmt.Println(i, "-----", item)
784                 }
785         }
786 }
787
788 func updateTxFeed(client *rpc.Client, args []string) {
789         if len(args) != 2 {
790                 fatalln("error:createTxFeed need arguments")
791         }
792         type In struct {
793                 Alias  string
794                 Filter string
795         }
796         var in In
797         in.Alias = args[0]
798         in.Filter = args[1]
799         client.Call(context.Background(), "/update-transaction-feed", in, nil)
800 }
801
802 func deleteTxFeed(client *rpc.Client, args []string) {
803         if len(args) != 1 {
804                 fatalln("error:deleteTxFeed use args alias")
805         }
806         type In struct {
807                 Alias string `json:"alias,omitempty"`
808         }
809         var in In
810         in.Alias = args[0]
811         client.Call(context.Background(), "/delete-transaction-feed", in, nil)
812 }
813
814 func listAccounts(client *rpc.Client, args []string) {
815         if len(args) != 0 {
816                 fatalln("error:listAccounts not use args")
817         }
818         type requestQuery struct {
819                 Filter       string        `json:"filter,omitempty"`
820                 FilterParams []interface{} `json:"filter_params,omitempty"`
821                 SumBy        []string      `json:"sum_by,omitempty"`
822                 PageSize     int           `json:"page_size"`
823                 AscLongPoll  bool          `json:"ascending_with_long_poll,omitempty"`
824                 Timeout      json.Duration `json:"timeout"`
825                 After        string        `json:"after"`
826                 StartTimeMS  uint64        `json:"start_time,omitempty"`
827                 EndTimeMS    uint64        `json:"end_time,omitempty"`
828                 TimestampMS  uint64        `json:"timestamp,omitempty"`
829                 Type         string        `json:"type"`
830                 Aliases      []string      `json:"aliases,omitempty"`
831         }
832         var in requestQuery
833
834         responses := make([]interface{}, 0)
835
836         client.Call(context.Background(), "/list-accounts", in, &responses)
837         if len(responses) > 0 {
838                 for i, item := range responses {
839                         fmt.Println(i, "-----", item)
840                 }
841         }
842 }
843
844 func listAssets(client *rpc.Client, args []string) {
845         if len(args) != 0 {
846                 fatalln("error:listAssets not use args")
847         }
848         type requestQuery struct {
849                 Filter       string        `json:"filter,omitempty"`
850                 FilterParams []interface{} `json:"filter_params,omitempty"`
851                 SumBy        []string      `json:"sum_by,omitempty"`
852                 PageSize     int           `json:"page_size"`
853                 AscLongPoll  bool          `json:"ascending_with_long_poll,omitempty"`
854                 Timeout      json.Duration `json:"timeout"`
855                 After        string        `json:"after"`
856                 StartTimeMS  uint64        `json:"start_time,omitempty"`
857                 EndTimeMS    uint64        `json:"end_time,omitempty"`
858                 TimestampMS  uint64        `json:"timestamp,omitempty"`
859                 Type         string        `json:"type"`
860                 Aliases      []string      `json:"aliases,omitempty"`
861         }
862         var in requestQuery
863         responses := make([]interface{}, 0)
864
865         client.Call(context.Background(), "/list-assets", in, &responses)
866         if len(responses) > 0 {
867                 for i, item := range responses {
868                         fmt.Println(i, "-----", item)
869                 }
870         }
871 }
872
873 func listTxFeeds(client *rpc.Client, args []string) {
874         fmt.Println("listTxFeeds")
875         if len(args) != 0 {
876                 fatalln("error:listTxFeeds not use args")
877         }
878         type requestQuery struct {
879                 Filter       string        `json:"filter,omitempty"`
880                 FilterParams []interface{} `json:"filter_params,omitempty"`
881                 SumBy        []string      `json:"sum_by,omitempty"`
882                 PageSize     int           `json:"page_size"`
883                 AscLongPoll  bool          `json:"ascending_with_long_poll,omitempty"`
884                 Timeout      json.Duration `json:"timeout"`
885                 After        string        `json:"after"`
886                 StartTimeMS  uint64        `json:"start_time,omitempty"`
887                 EndTimeMS    uint64        `json:"end_time,omitempty"`
888                 TimestampMS  uint64        `json:"timestamp,omitempty"`
889                 Type         string        `json:"type"`
890                 Aliases      []string      `json:"aliases,omitempty"`
891         }
892         var in requestQuery
893
894         responses := make([]interface{}, 0)
895
896         client.Call(context.Background(), "/list-transaction-feeds", in, &responses)
897         if len(responses) > 0 {
898                 for i, item := range responses {
899                         fmt.Println(i, "-----", item)
900                 }
901         }
902
903 }
904
905 func listTransactions(client *rpc.Client, args []string) {
906         if len(args) != 0 {
907                 fatalln("error:listTransactions not use args")
908         }
909         type requestQuery struct {
910                 Filter       string        `json:"filter,omitempty"`
911                 FilterParams []interface{} `json:"filter_params,omitempty"`
912                 SumBy        []string      `json:"sum_by,omitempty"`
913                 PageSize     int           `json:"page_size"`
914                 AscLongPoll  bool          `json:"ascending_with_long_poll,omitempty"`
915                 Timeout      json.Duration `json:"timeout"`
916                 After        string        `json:"after"`
917                 StartTimeMS  uint64        `json:"start_time,omitempty"`
918                 EndTimeMS    uint64        `json:"end_time,omitempty"`
919                 TimestampMS  uint64        `json:"timestamp,omitempty"`
920                 Type         string        `json:"type"`
921                 Aliases      []string      `json:"aliases,omitempty"`
922         }
923         var in requestQuery
924         var rawResponse []byte
925         var response blockchain.Response
926
927         client.Call(context.Background(), "/list-transactions", in, &rawResponse)
928
929         if err := stdjson.Unmarshal(rawResponse, &response); err != nil {
930                 fmt.Println(err)
931         }
932
933         if response.Status != blockchain.SUCCESS {
934                 fmt.Println(response.Msg)
935                 return
936         }
937
938         for i, item := range response.Data {
939                 fmt.Println(i, "-----", item)
940         }
941 }
942
943 func listBalances(client *rpc.Client, args []string) {
944         if len(args) != 0 {
945                 fatalln("error:listBalances not use args")
946         }
947         type requestQuery struct {
948                 Filter       string        `json:"filter,omitempty"`
949                 FilterParams []interface{} `json:"filter_params,omitempty"`
950                 SumBy        []string      `json:"sum_by,omitempty"`
951                 PageSize     int           `json:"page_size"`
952                 AscLongPoll  bool          `json:"ascending_with_long_poll,omitempty"`
953                 Timeout      json.Duration `json:"timeout"`
954                 After        string        `json:"after"`
955                 StartTimeMS  uint64        `json:"start_time,omitempty"`
956                 EndTimeMS    uint64        `json:"end_time,omitempty"`
957                 TimestampMS  uint64        `json:"timestamp,omitempty"`
958                 Type         string        `json:"type"`
959                 Aliases      []string      `json:"aliases,omitempty"`
960         }
961
962         var in requestQuery
963         responses := make([]interface{}, 0)
964
965         client.Call(context.Background(), "/list-balances", in, &responses)
966         if len(responses) > 0 {
967                 for i, item := range responses {
968                         fmt.Println(i, "-----", item)
969                 }
970         }
971 }
972
973 func listUnspentOutputs(client *rpc.Client, args []string) {
974         if len(args) != 0 {
975                 fatalln("error:listUnspentOutputs not use args")
976         }
977         type requestQuery struct {
978                 Filter       string        `json:"filter,omitempty"`
979                 FilterParams []interface{} `json:"filter_params,omitempty"`
980                 SumBy        []string      `json:"sum_by,omitempty"`
981                 PageSize     int           `json:"page_size"`
982                 AscLongPoll  bool          `json:"ascending_with_long_poll,omitempty"`
983                 Timeout      json.Duration `json:"timeout"`
984                 After        string        `json:"after"`
985                 StartTimeMS  uint64        `json:"start_time,omitempty"`
986                 EndTimeMS    uint64        `json:"end_time,omitempty"`
987                 TimestampMS  uint64        `json:"timestamp,omitempty"`
988                 Type         string        `json:"type"`
989                 Aliases      []string      `json:"aliases,omitempty"`
990         }
991         var in requestQuery
992         responses := make([]interface{}, 0)
993
994         client.Call(context.Background(), "/list-unspent-outputs", in, &responses)
995         if len(responses) > 0 {
996                 for i, item := range responses {
997                         fmt.Println(i, "-----", item)
998                 }
999         }
1000 }
1001
1002 func createAccessToken(client *rpc.Client, args []string) {
1003         if len(args) != 1 {
1004                 fatalln("error:createAccessToken use args id")
1005         }
1006         type Token struct {
1007                 ID   string `json:"id"`
1008                 Type string `json:"type"`
1009         }
1010         var token Token
1011         token.ID = args[0]
1012
1013         var response interface{}
1014
1015         client.Call(context.Background(), "/create-access-token", &token, &response)
1016         fmt.Println(response)
1017 }
1018
1019 func listAccessTokens(client *rpc.Client, args []string) {
1020         if len(args) != 0 {
1021                 fatalln("error:listAccessTokens not use args")
1022         }
1023         var response interface{}
1024         client.Call(context.Background(), "/list-access-token", nil, &response)
1025         fmt.Println(response)
1026 }
1027
1028 func deleteAccessToken(client *rpc.Client, args []string) {
1029         if len(args) != 1 {
1030                 fatalln("error:deleteAccessToken use args id")
1031         }
1032         type Token struct {
1033                 ID     string `json:"id"`
1034                 Secert string `json:"secert,omitempty"`
1035         }
1036         var token Token
1037         token.ID = args[0]
1038         var response interface{}
1039         client.Call(context.Background(), "/delete-access-token", &token, &response)
1040         fmt.Println(response)
1041 }
1042
1043 func checkAccessToken(client *rpc.Client, args []string) {
1044         if len(args) != 1 {
1045                 fatalln("error:deleteAccessToken use args token")
1046         }
1047         type Token struct {
1048                 ID     string `json:"id"`
1049                 Secret string `json:"secret,omitempty"`
1050         }
1051         var token Token
1052         inputs := strings.Split(args[0], ":")
1053         token.ID = inputs[0]
1054         token.Secret = inputs[1]
1055         var response interface{}
1056         client.Call(context.Background(), "/check-access-token", &token, &response)
1057         fmt.Println(response)
1058 }
1059
1060 func createKey(client *rpc.Client, args []string) {
1061         if len(args) != 2 {
1062                 fatalln("error: createKey args not vaild")
1063         }
1064         type Key struct {
1065                 Alias    string
1066                 Password string
1067         }
1068         var key Key
1069         var response map[string]interface{}
1070         key.Alias = args[0]
1071         key.Password = args[1]
1072
1073         client.Call(context.Background(), "/create-key", &key, &response)
1074         fmt.Printf("Alias: %v,  XPub: %v, File: %v\n", response["alias"], response["xpub"], response["file"])
1075 }
1076
1077 func deleteKey(client *rpc.Client, args []string) {
1078         if len(args) != 2 {
1079                 fatalln("error: deleteKey args not vaild")
1080         }
1081         type Key struct {
1082                 Password string
1083                 XPub     chainkd.XPub `json:"xpubs"`
1084         }
1085         var key Key
1086         xpub := new(chainkd.XPub)
1087         data, err := hex.DecodeString(args[1])
1088         if err != nil {
1089                 fatalln("error: deletKey %v", err)
1090         }
1091         copy(xpub[:], data)
1092         key.Password = args[0]
1093         key.XPub = *xpub
1094         client.Call(context.Background(), "/delete-key", &key, nil)
1095 }
1096
1097 func listKeys(client *rpc.Client, args []string) {
1098         if len(args) != 2 {
1099                 fatalln("error: listKeys args not vaild")
1100         }
1101
1102         type requestQuery struct {
1103                 Filter       string        `json:"filter,omitempty"`
1104                 FilterParams []interface{} `json:"filter_params,omitempty"`
1105                 SumBy        []string      `json:"sum_by,omitempty"`
1106                 PageSize     int           `json:"page_size"`
1107                 AscLongPoll  bool          `json:"ascending_with_long_poll,omitempty"`
1108                 Timeout      json.Duration `json:"timeout"`
1109                 After        string        `json:"after"`
1110                 StartTimeMS  uint64        `json:"start_time,omitempty"`
1111                 EndTimeMS    uint64        `json:"end_time,omitempty"`
1112                 TimestampMS  uint64        `json:"timestamp,omitempty"`
1113                 Type         string        `json:"type"`
1114                 Aliases      []string      `json:"aliases,omitempty"`
1115         }
1116         var in requestQuery
1117         in.After = args[0]
1118         in.PageSize, _ = strconv.Atoi(args[1])
1119         var response map[string][]interface{}
1120         client.Call(context.Background(), "/list-keys", &in, &response)
1121         for i, item := range response["items"] {
1122                 key := item.(map[string]interface{})
1123                 fmt.Printf("---No.%v Alias:%v Address:%v File:%v\n", i, key["alias"], key["address"], key["file"])
1124         }
1125 }
1126
1127 func signTransactions(client *rpc.Client, args []string) {
1128
1129         // sign-transaction
1130         type param struct {
1131                 Auth  string
1132                 Txs   []*txbuilder.Template `json:"transactions"`
1133                 XPubs chainkd.XPub          `json:"xpubs"`
1134                 XPrv  chainkd.XPrv          `json:"xprv"`
1135         }
1136
1137         var in param
1138         var xprv chainkd.XPrv
1139         var xpub chainkd.XPub
1140         var err error
1141
1142         if len(args) == 3 {
1143                 err = xpub.UnmarshalText([]byte(args[1]))
1144                 if err == nil {
1145                         fmt.Printf("xpub:%v\n", xpub)
1146                 } else {
1147                         fmt.Printf("xpub unmarshal error:%v\n", xpub)
1148                 }
1149                 in.XPubs = xpub
1150                 in.Auth = args[2]
1151
1152         } else if len(args) == 2 {
1153                 err = xprv.UnmarshalText([]byte(args[1]))
1154                 if err == nil {
1155                         fmt.Printf("xprv:%v\n", xprv)
1156                 } else {
1157                         fmt.Printf("xprv unmarshal error:%v\n", xprv)
1158                 }
1159                 in.XPrv = xprv
1160
1161         } else {
1162                 fatalln("error: signTransaction need args: [tpl file name] [xPub] [password], 3 args not equal"+
1163                         "or [tpl file name] [xPrv], 2 args not equal ", len(args))
1164         }
1165
1166         var tpl txbuilder.Template
1167         file, _ := os.Open(args[0])
1168         tpl_byte := make([]byte, 10000)
1169         file.Read(tpl_byte)
1170         fmt.Printf("tpl_byte:%v\n", string(tpl_byte))
1171         err = stdjson.Unmarshal(bytes.Trim(tpl_byte, "\x00"), &tpl)
1172         fmt.Printf("tpl:%v, err:%v\n", tpl, err)
1173         in.Txs = []*txbuilder.Template{&tpl}
1174
1175         var response = make([]interface{}, 1)
1176         client.Call(context.Background(), "/sign-transactions", &in, &response)
1177         fmt.Printf("sign response:%v\n", response)
1178 }
1179
1180 func resetPassword(client *rpc.Client, args []string) {
1181         if len(args) != 3 {
1182                 fatalln("error: resetpassword args not vaild")
1183         }
1184         type Key struct {
1185                 OldPassword string
1186                 NewPassword string
1187                 XPub        chainkd.XPub `json:"xpubs"`
1188         }
1189         var key Key
1190         xpub := new(chainkd.XPub)
1191         data, err := hex.DecodeString(args[2])
1192         if err != nil {
1193                 fatalln("error: resetPassword %v", err)
1194         }
1195         copy(xpub[:], data)
1196         key.OldPassword = args[0]
1197         key.NewPassword = args[1]
1198         key.XPub = *xpub
1199         client.Call(context.Background(), "/reset-password", &key, nil)
1200 }
1201
1202 func updateAlias(client *rpc.Client, args []string) {
1203         if len(args) != 3 {
1204                 fatalln("error: resetpassword args not vaild")
1205         }
1206         type Key struct {
1207                 Password string
1208                 NewAlias string
1209                 XPub     chainkd.XPub `json:"xpubs"`
1210         }
1211         var key Key
1212         xpub := new(chainkd.XPub)
1213         data, err := hex.DecodeString(args[2])
1214         if err != nil {
1215                 fatalln("error: resetPassword %v", err)
1216         }
1217         copy(xpub[:], data)
1218         key.Password = args[0]
1219         key.NewAlias = args[1]
1220         key.XPub = *xpub
1221         client.Call(context.Background(), "/update-alias", &key, nil)
1222 }
1223
1224 func netInfo(client *rpc.Client, args []string) {
1225         var response interface{}
1226         client.Call(context.Background(), "/net-info", nil, &response)
1227         fmt.Printf("net info:%v\n", response)
1228 }
1229
1230 func getBestBlockHash(client *rpc.Client, args []string) {
1231         var response interface{}
1232         client.Call(context.Background(), "/get-best-block-hash", nil, &response)
1233         fmt.Printf("best-block-hash:%v\n", response)
1234 }
1235
1236 func getBlockHeaderByHash(client *rpc.Client, args []string) {
1237         if len(args) != 1 {
1238                 fatalln("error: get-block-header-by-hash args not valid: get-block-header-by-hash [hash]")
1239         }
1240         var response interface{}
1241         client.Call(context.Background(), "/get-block-header-by-hash", args[0], &response)
1242         fmt.Printf("block header: %v\n", response)
1243 }
1244
1245 func getBlockByHash(client *rpc.Client, args []string) {
1246         if len(args) != 1 {
1247                 fatalln("error: get-block-by-hash args not valid: get-block-by-hash [hash]")
1248         }
1249         var response interface{}
1250         client.Call(context.Background(), "/get-block-by-hash", args[0], &response)
1251         fmt.Printf("%v\n", response)
1252 }