OSDN Git Service

Merge pull request #12 from Bytom/develop
[bytom/bytom.git] / cmd / bytomcli / main.go
1 // Command corectl provides miscellaneous control functions for a Chain Core.
2 package main
3
4 import (
5         "bytes"
6         "context"
7         "flag"
8         "fmt"
9         "io"
10         "net"
11         "net/http"
12         "os"
13         "path/filepath"
14         "strings"
15         "time"
16
17         "github.com/bytom/blockchain"
18         "github.com/bytom/blockchain/rpc"
19         "github.com/bytom/crypto/ed25519"
20         "github.com/bytom/env"
21         "github.com/bytom/errors"
22         "github.com/bytom/log"
23         "github.com/bytom/crypto/ed25519/chainkd"
24 )
25
26 // config vars
27 var (
28         home    = blockchain.HomeDirFromEnvironment()
29         coreURL = env.String("BYTOM_URL", "http://localhost:1999")
30
31         // build vars; initialized by the linker
32         buildTag    = "?"
33         buildCommit = "?"
34         buildDate   = "?"
35 )
36
37 // We collect log output in this buffer,
38 // and display it only when there's an error.
39 var logbuf bytes.Buffer
40
41 type command struct {
42         f func(*rpc.Client, []string)
43 }
44
45 type grantReq struct {
46         Policy    string      `json:"policy"`
47         GuardType string      `json:"guard_type"`
48         GuardData interface{} `json:"guard_data"`
49 }
50
51 var commands = map[string]*command{
52         "create-block-keypair": {createBlockKeyPair},
53         "reset":                {reset},
54         "grant":                {grant},
55         "revoke":               {revoke},
56         "wait":                 {wait},
57         "create-account":       {createAccount},
58         "update-account-tags":  {updateAccountTags},
59         "create-asset":         {createAsset},
60         "update-asset-tags":    {updateAssetTags},
61         "build-transaction": {buildTransaction},
62 }
63
64 func main() {
65         log.SetOutput(&logbuf)
66         env.Parse()
67
68         if len(os.Args) >= 2 && os.Args[1] == "-version" {
69                 var version string
70                 if buildTag != "?" {
71                         // build tag with bytom- prefix indicates official release
72                         version = strings.TrimPrefix(buildTag, "bytom-")
73                 } else {
74                         // version of the form rev123 indicates non-release build
75                         //version = rev.ID
76                 }
77                 fmt.Printf("bytomcli %s\n", version)
78                 fmt.Printf("build-commit: %v\n", buildCommit)
79                 fmt.Printf("build-date: %v\n", buildDate)
80                 return
81         }
82
83         if len(os.Args) < 2 {
84                 help(os.Stdout)
85                 os.Exit(0)
86         }
87         cmd := commands[os.Args[1]]
88         if cmd == nil {
89                 fmt.Fprintln(os.Stderr, "unknown command:", os.Args[1])
90                 help(os.Stderr)
91                 os.Exit(1)
92         }
93         cmd.f(mustRPCClient(), os.Args[2:])
94 }
95
96
97 func createBlockKeyPair(client *rpc.Client, args []string) {
98         if len(args) != 0 {
99                 fatalln("error: create-block-keypair takes no args")
100         }
101         pub := struct {
102                 Pub ed25519.PublicKey
103         }{}
104         err := client.Call(context.Background(), "/mockhsm/create-block-key", nil, &pub)
105         dieOnRPCError(err)
106         fmt.Printf("%x\n", pub.Pub)
107 }
108
109 // reset will attempt a reset rpc call on a remote core. If the
110 // core is not configured with reset capabilities an error is returned.
111 func reset(client *rpc.Client, args []string) {
112         if len(args) != 0 {
113                 fatalln("error: reset takes no args")
114         }
115
116         req := map[string]bool{
117                 "Everything": true,
118         }
119
120         err := client.Call(context.Background(), "/reset", req, nil)
121         dieOnRPCError(err)
122 }
123
124 func grant(client *rpc.Client, args []string) {
125         editAuthz(client, args, "grant")
126 }
127
128 func revoke(client *rpc.Client, args []string) {
129         editAuthz(client, args, "revoke")
130 }
131
132 func editAuthz(client *rpc.Client, args []string, action string) {
133         usage := "usage: corectl " + action + " [policy] [guard]"
134         var flags flag.FlagSet
135
136         flags.Usage = func() {
137                 fmt.Fprintln(os.Stderr, usage)
138                 fmt.Fprintln(os.Stderr, `
139 Where guard is one of:
140   token=[id]   to affect an access token
141   CN=[name]    to affect an X.509 Common Name
142   OU=[name]    to affect an X.509 Organizational Unit
143
144 The type of guard (before the = sign) is case-insensitive.
145 `)
146                 os.Exit(1)
147         }
148         flags.Parse(args)
149         args = flags.Args()
150         if len(args) != 2 {
151                 fatalln(usage)
152         }
153
154         req := grantReq{Policy: args[0]}
155
156         switch typ, data := splitAfter2(args[1], "="); strings.ToUpper(typ) {
157         case "TOKEN=":
158                 req.GuardType = "access_token"
159                 req.GuardData = map[string]interface{}{"id": data}
160         case "CN=":
161                 req.GuardType = "x509"
162                 req.GuardData = map[string]interface{}{"subject": map[string]string{"CN": data}}
163         case "OU=":
164                 req.GuardType = "x509"
165                 req.GuardData = map[string]interface{}{"subject": map[string]string{"OU": data}}
166         default:
167                 fmt.Fprintln(os.Stderr, "unknown guard type", typ)
168                 fatalln(usage)
169         }
170
171         path := map[string]string{
172                 "grant":  "/create-authorization-grant",
173                 "revoke": "/delete-authorization-grant",
174         }[action]
175         err := client.Call(context.Background(), path, req, nil)
176         dieOnRPCError(err)
177 }
178
179 func mustRPCClient() *rpc.Client {
180         // TODO(kr): refactor some of this cert-loading logic into bytom/blockchain
181         // and use it from cored as well.
182         // Note that this function, unlike maybeUseTLS in cored,
183         // does not load the cert and key from env vars,
184         // only from the filesystem.
185         certFile := filepath.Join(home, "tls.crt")
186         keyFile := filepath.Join(home, "tls.key")
187         config, err := blockchain.TLSConfig(certFile, keyFile, "")
188         if err == blockchain.ErrNoTLS {
189                 return &rpc.Client{BaseURL: *coreURL}
190         } else if err != nil {
191                 fatalln("error: loading TLS cert:", err)
192         }
193
194         t := &http.Transport{
195                 DialContext: (&net.Dialer{
196                         Timeout:   30 * time.Second,
197                         KeepAlive: 30 * time.Second,
198                         DualStack: true,
199                 }).DialContext,
200                 MaxIdleConns:          100,
201                 IdleConnTimeout:       90 * time.Second,
202                 TLSClientConfig:       config,
203                 TLSHandshakeTimeout:   10 * time.Second,
204                 ExpectContinueTimeout: 1 * time.Second,
205         }
206
207         url := *coreURL
208         if strings.HasPrefix(url, "http:") {
209                 url = "https:" + url[5:]
210         }
211
212         return &rpc.Client{
213                 BaseURL: url,
214                 Client:  &http.Client{Transport: t},
215         }
216 }
217
218 func fatalln(v ...interface{}) {
219         io.Copy(os.Stderr, &logbuf)
220         fmt.Fprintln(os.Stderr, v...)
221         os.Exit(2)
222 }
223
224 func dieOnRPCError(err error, prefixes ...interface{}) {
225         if err == nil {
226                 return
227         }
228
229         io.Copy(os.Stderr, &logbuf)
230
231         if len(prefixes) > 0 {
232                 fmt.Fprintln(os.Stderr, prefixes...)
233         }
234
235         if msgErr, ok := errors.Root(err).(rpc.ErrStatusCode); ok && msgErr.ErrorData != nil {
236                 fmt.Fprintln(os.Stderr, "RPC error:", msgErr.ErrorData.ChainCode, msgErr.ErrorData.Message)
237                 if msgErr.ErrorData.Detail != "" {
238                         fmt.Fprintln(os.Stderr, "Detail:", msgErr.ErrorData.Detail)
239                 }
240         } else {
241                 fmt.Fprintln(os.Stderr, "RPC error:", err)
242         }
243
244         os.Exit(2)
245 }
246
247 func help(w io.Writer) {
248         fmt.Fprintln(w, "usage: corectl [-version] [command] [arguments]")
249         fmt.Fprint(w, "\nThe commands are:\n\n")
250         for name := range commands {
251                 fmt.Fprintln(w, "\t", name)
252         }
253         fmt.Fprint(w, "\nFlags:\n")
254         fmt.Fprintln(w, "\t-version   print version information")
255         fmt.Fprintln(w)
256 }
257
258 // splitAfter2 is like strings.SplitAfterN with n=2.
259 // If sep is not in s, it returns a="" and b=s.
260 func splitAfter2(s, sep string) (a, b string) {
261         i := strings.Index(s, sep)
262         k := i + len(sep)
263         return s[:k], s[k:]
264 }
265
266 func wait(client *rpc.Client, args []string) {
267         if len(args) != 0 {
268                 fatalln("error: wait takes no args")
269         }
270
271         for {
272                 err := client.Call(context.Background(), "/info", nil, nil)
273                 if err == nil {
274                         break
275                 }
276
277                 if statusErr, ok := errors.Root(err).(rpc.ErrStatusCode); ok && statusErr.StatusCode/100 != 5 {
278                         break
279                 }
280
281                 time.Sleep(500 * time.Millisecond)
282         }
283 }
284
285 func createAccount(client *rpc.Client, args []string) {
286         if len(args) != 1 {
287                 fatalln("error: createAccount takes no args")
288         }
289         xprv, err := chainkd.NewXPrv(nil)
290         if err != nil {
291                 fatalln("NewXprv error.")
292         }
293         xpub := xprv.XPub()
294         fmt.Printf("xprv:%v\n", xprv)
295         fmt.Printf("xpub:%v\n", xpub)
296         type Ins struct {
297             RootXPubs []chainkd.XPub `json:"root_xpubs"`
298                 Quorum    int
299                 Alias     string
300                 Tags      map[string]interface{}
301                 ClientToken string `json:"client_token"`
302         }
303         var ins Ins
304         ins.RootXPubs = []chainkd.XPub{xpub}
305         ins.Quorum = 1
306         ins.Alias = "aa"
307         ins.Tags = map[string]interface{}{"test_tag": "v0",}
308         ins.ClientToken = args[0]
309         responses := make([]interface{}, 50)
310         client.Call(context.Background(), "/create-account", &[]Ins{ins,}, &responses)
311         //dieOnRPCError(err)
312         fmt.Printf("responses:%v\n", responses)
313 }
314
315 func createAsset(client *rpc.Client, args []string) {
316         if len(args) != 1 {
317                 fatalln("error: createAsset 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                 Definition  map[string]interface{}
332                 ClientToken string `json:"client_token"`
333         }
334         var ins Ins
335         ins.RootXPubs = []chainkd.XPub{xpub}
336         ins.Quorum = 1
337         ins.Alias = "aa"
338         ins.Tags = map[string]interface{}{"test_tag": "v0",}
339         ins.Definition = map[string]interface{}{"test_definition": "v0"}
340         ins.ClientToken = args[0]
341         responses := make([]interface{}, 50)
342         client.Call(context.Background(), "/create-asset", &[]Ins{ins,}, &responses)
343         //dieOnRPCError(err)
344         fmt.Printf("responses:%v\n", responses)
345 }
346
347 func updateAccountTags(client *rpc.Client,args []string){
348         if len(args) != 0{
349                 fatalln("error:updateAccountTags not use args")
350         }
351         type Ins struct {
352         ID    *string
353         Alias *string
354         Tags  map[string]interface{} `json:"tags"`
355 }
356         var ins Ins
357         aa := "1234"
358         alias := "asdfg"
359         ins.ID = &aa
360         ins.Alias = &alias
361         ins.Tags = map[string]interface{}{"test_tag": "v0",}
362         responses := make([]interface{}, 50)
363         client.Call(context.Background(), "/update-account-tags", &[]Ins{ins,}, &responses)
364         fmt.Printf("responses:%v\n", responses)
365 }
366
367 func updateAssetTags(client *rpc.Client, args []string){
368         if len(args) != 0{
369                         fatalln("error:updateAccountTags not use args")
370         }
371         type Ins struct {
372         ID    *string
373         Alias *string
374         Tags  map[string]interface{} `json:"tags"`
375         }
376         var ins Ins
377         id := "123456"
378         alias := "asdfg"
379         ins.ID = &id
380         ins.Alias = &alias
381         ins.Tags = map[string]interface{}{"test_tag": "v0",}
382         responses := make([]interface{}, 50)
383         client.Call(context.Background(), "/update-asset-tags", &[]Ins{ins,}, &responses)
384         fmt.Printf("responses:%v\n", responses)
385 }
386
387 func buildTransaction(client *rpc.Client, args []string) {
388         if len(args) != 0 {
389                 fatalln("error:updateAccountTags not use args")
390         }
391 }