github.com/mavryk-network/mvgo@v1.19.9/examples/rpc/main.go (about) 1 // Copyright (c) 2020-2021 Blockwatch Data Inc. 2 // Author: alex@blockwatch.cc 3 4 // RPC examples 5 package main 6 7 import ( 8 "context" 9 "encoding/json" 10 "flag" 11 "fmt" 12 "os" 13 "os/signal" 14 "strconv" 15 "strings" 16 "syscall" 17 18 "github.com/mavryk-network/mvgo/mavryk" 19 "github.com/mavryk-network/mvgo/micheline" 20 "github.com/mavryk-network/mvgo/rpc" 21 22 "github.com/echa/log" 23 ) 24 25 var ( 26 flags = flag.NewFlagSet("rpc", flag.ContinueOnError) 27 verbose bool 28 debug bool 29 node string 30 ) 31 32 func init() { 33 flags.Usage = func() {} 34 flags.BoolVar(&verbose, "v", false, "Be verbose") 35 flags.BoolVar(&debug, "d", false, "Enable debug mode") 36 flags.StringVar(&node, "node", "https://rpc.tzpro.io", "Tezos node url") 37 } 38 39 func main() { 40 if err := flags.Parse(os.Args[1:]); err != nil { 41 if err == flag.ErrHelp { 42 fmt.Println("Usage: rpc [args] <cmd> [sub-args]") 43 fmt.Printf("\nCommands\n") 44 fmt.Printf(" block <hash>|head show block info\n") 45 fmt.Printf(" op <block>/<list>/<pos> show operation info\n") 46 fmt.Printf(" cost <block>/<list>/<pos> show operation cost\n") 47 fmt.Printf(" contract <hash> show contract info\n") 48 fmt.Printf(" search <ops> <lvl> output blocks containing operations in list\n") 49 fmt.Printf(" bootstrap wait until node is bootstrapped\n") 50 fmt.Printf(" monitor wait and show new heads as they are baked\n") 51 fmt.Printf("\nGlobal arguments\n") 52 flags.PrintDefaults() 53 os.Exit(0) 54 } 55 log.Fatal("Error:", err) 56 } 57 58 if err := run(); err != nil { 59 log.Fatal("Error:", err) 60 } 61 } 62 63 func run() error { 64 if flags.NArg() < 1 { 65 return fmt.Errorf("Command required") 66 } 67 68 switch { 69 case debug: 70 log.SetLevel(log.LevelDebug) 71 case verbose: 72 log.SetLevel(log.LevelInfo) 73 default: 74 log.SetLevel(log.LevelWarn) 75 } 76 rpc.UseLogger(log.Log) 77 78 ctx, cancel := context.WithCancel(context.Background()) 79 defer cancel() 80 81 log.Infof("Using RPC %s", node) 82 c, err := rpc.NewClient(node, nil) 83 if err != nil { 84 return err 85 } 86 c.Log = log.Log 87 88 switch flags.Arg(0) { 89 case "block": 90 h := flags.Arg(1) 91 if h == "" { 92 return fmt.Errorf("Missing block identifier") 93 } 94 if h == "head" { 95 return fetchHead(ctx, c) 96 } else { 97 // try parsing arg as height (i.e. integer) 98 if height, err := strconv.ParseInt(h, 10, 64); err == nil { 99 return fetchBlockHeight(ctx, c, height) 100 } 101 // otherwise, parse as block hash 102 h, err := mavryk.ParseBlockHash(flags.Arg(1)) 103 if err != nil { 104 return err 105 } 106 return fetchBlock(ctx, c, h) 107 } 108 case "op": 109 h := flags.Arg(1) 110 if h == "" { 111 return fmt.Errorf("Missing operation identifier") 112 } 113 parts := strings.SplitN(h, "/", 3) 114 if len(parts) != 3 { 115 return fmt.Errorf("Invalid operation identifier (form: block-hash:list:pos)") 116 } 117 bid := rpc.BlockAlias(parts[0]) 118 list, err := strconv.Atoi(parts[1]) 119 if err != nil { 120 return fmt.Errorf("Invalid list identifier: %v", err) 121 } 122 pos, err := strconv.Atoi(parts[2]) 123 if err != nil { 124 return fmt.Errorf("Invalid position identifier: %v", err) 125 } 126 return showOpInfo(ctx, c, bid, list, pos) 127 128 case "cost": 129 h := flags.Arg(1) 130 if h == "" { 131 return fmt.Errorf("Missing operation identifier") 132 } 133 parts := strings.SplitN(h, "/", 3) 134 if len(parts) != 3 { 135 return fmt.Errorf("Invalid operation identifier (form: block-hash:list:pos)") 136 } 137 bid := rpc.BlockAlias(parts[0]) 138 list, err := strconv.Atoi(parts[1]) 139 if err != nil { 140 return fmt.Errorf("Invalid list identifier: %v", err) 141 } 142 pos, err := strconv.Atoi(parts[2]) 143 if err != nil { 144 return fmt.Errorf("Invalid position identifier: %v", err) 145 } 146 return showOpCost(ctx, c, bid, list, pos) 147 148 case "bootstrap": 149 return waitBootstrap(ctx, c) 150 case "monitor": 151 return monitorBlocks(ctx, c) 152 case "contract": 153 a := flags.Arg(1) 154 if a == "" { 155 return fmt.Errorf("Missing contract address") 156 } 157 addr, err := mavryk.ParseAddress(a) 158 if err != nil { 159 return err 160 } 161 if addr.Type() != mavryk.AddressTypeContract { 162 return fmt.Errorf("%s is not a smart contract address", a) 163 } 164 return showContractInfo(ctx, c, addr) 165 case "search": 166 height, _ := strconv.ParseInt(flags.Arg(2), 10, 64) 167 return searchOps(ctx, c, flags.Arg(1), height) 168 default: 169 return fmt.Errorf("unknown command %s", flags.Arg(0)) 170 } 171 } 172 173 func fetchBlockHeight(ctx context.Context, c *rpc.Client, height int64) error { 174 b, err := c.GetBlockHeight(ctx, height) 175 if err != nil { 176 return err 177 } 178 printBlock(b) 179 return nil 180 } 181 182 func fetchBlock(ctx context.Context, c *rpc.Client, blockID mavryk.BlockHash) error { 183 b, err := c.GetBlock(ctx, blockID) 184 if err != nil { 185 return err 186 } 187 printBlock(b) 188 return nil 189 } 190 191 func fetchHead(ctx context.Context, c *rpc.Client) error { 192 head, err := c.GetTipHeader(ctx) 193 if err != nil { 194 return err 195 } 196 printHead(head) 197 return nil 198 } 199 200 func printHead(h *rpc.BlockHeader) { 201 fmt.Printf("Block %d %s %s\n", h.Level, h.Hash, h.Timestamp) 202 } 203 204 func printBlock(b *rpc.Block) { 205 fmt.Printf("Height %d (%d)\n", b.GetLevel(), b.GetCycle()) 206 fmt.Printf("Block %s\n", b.Hash) 207 if b.Header.PayloadHash.IsValid() { 208 fmt.Printf("Round %d\n", b.Header.PayloadRound) 209 fmt.Printf("Payload %s\n", b.Header.PayloadHash) 210 } else { 211 fmt.Printf("Priority %d\n", b.Header.Priority) 212 fmt.Printf("Payload %s\n", b.Header.OperationsHash) 213 } 214 fmt.Printf("Parent %s\n", b.Header.Predecessor) 215 fmt.Printf("Time %s\n", b.Header.Timestamp) 216 217 // count operations and details 218 ops := make(map[mavryk.OpType]int) 219 var count int 220 for _, v := range b.Operations { 221 for _, vv := range v { 222 for _, op := range vv.Contents { 223 kind := op.Kind() 224 count++ 225 if c, ok := ops[kind]; ok { 226 ops[kind] = c + 1 227 } else { 228 ops[kind] = 1 229 } 230 } 231 } 232 } 233 fmt.Printf("Ops %d: ", count) 234 comma := "" 235 for n, c := range ops { 236 fmt.Printf("%s%d %s", comma, c, n) 237 comma = ", " 238 } 239 fmt.Println() 240 fmt.Println() 241 } 242 243 func waitBootstrap(ctx context.Context, c *rpc.Client) error { 244 mon := rpc.NewBootstrapMonitor() 245 defer mon.Close() 246 if err := c.MonitorBootstrapped(ctx, mon); err != nil { 247 return err 248 } 249 ctx2, cancel := context.WithCancel(ctx) 250 stop := make(chan os.Signal, 1) 251 signal.Notify(stop, 252 syscall.SIGHUP, 253 syscall.SIGINT, 254 syscall.SIGTERM, 255 syscall.SIGQUIT, 256 ) 257 go func() { 258 select { 259 case <-stop: 260 fmt.Printf("Stopping monitor\n") 261 cancel() 262 case <-ctx.Done(): 263 } 264 }() 265 266 fmt.Printf("Waiting for chain to bootstrap... (cancel with Ctrl-C)\n\n") 267 268 for { 269 b, err := mon.Recv(ctx2) 270 if err != nil { 271 return err 272 } 273 if err := fetchBlock(ctx, c, b.Block); err != nil { 274 return err 275 } 276 } 277 } 278 279 func monitorBlocks(ctx context.Context, c *rpc.Client) error { 280 mon := rpc.NewBlockHeaderMonitor() 281 defer mon.Close() 282 if err := c.MonitorBlockHeader(ctx, mon); err != nil { 283 return err 284 } 285 286 ctx2, cancel := context.WithCancel(ctx) 287 stop := make(chan os.Signal, 1) 288 signal.Notify(stop, 289 syscall.SIGHUP, 290 syscall.SIGINT, 291 syscall.SIGTERM, 292 syscall.SIGQUIT, 293 ) 294 go func() { 295 select { 296 case <-stop: 297 fmt.Printf("Stopping monitor\n") 298 cancel() 299 case <-ctx.Done(): 300 } 301 }() 302 303 fmt.Printf("Waiting for new blocks... (cancel with Ctrl-C)\n\n") 304 for { 305 h, err := mon.Recv(ctx2) 306 if err != nil { 307 return err 308 } 309 if err := fetchBlock(ctx, c, h.Hash); err != nil { 310 return err 311 } 312 } 313 } 314 315 func searchOps(ctx context.Context, c *rpc.Client, ops string, start int64) error { 316 if ops == "deactivated" { 317 return searchDeactivations(ctx, c, start) 318 } 319 320 // find the current blockchain tip 321 tips, err := c.GetTips(ctx, 1, mavryk.BlockHash{}) 322 if err != nil { 323 return err 324 } 325 if len(tips) == 0 || len(tips[0]) == 0 { 326 return fmt.Errorf("invalid chain tip") 327 } 328 tip, err := c.GetBlock(ctx, tips[0][0]) 329 if err != nil { 330 return fmt.Errorf("Block %s failed: %w", tips[0][0], err) 331 } 332 333 // parse ops 334 oplist := make([]mavryk.OpType, 0) 335 eplist := make([]string, 0) 336 for _, op := range strings.Split(ops, ",") { 337 if strings.HasPrefix(op, "ep:") { 338 eplist = append(eplist, strings.TrimPrefix(op, "ep:")) 339 } else { 340 ot := mavryk.ParseOpType(op) 341 if !ot.IsValid() { 342 return fmt.Errorf("invalid operation type '%s'", op) 343 } 344 oplist = append(oplist, ot) 345 } 346 } 347 if len(eplist) > 0 { 348 oplist = append(oplist, mavryk.OpTypeTransaction) 349 } 350 351 // fetching blocks forward 352 height := start 353 enc := json.NewEncoder(os.Stdout) 354 enc.SetIndent("", " ") 355 opcount := make(map[mavryk.OpType]int) 356 epcount := make(map[string]int) 357 plog := log.NewProgressLogger(log.Log).SetEvent("block") 358 for { 359 b, err := c.GetBlockHeight(ctx, height) 360 if err != nil { 361 return fmt.Errorf("Block %d failed: %w", height, err) 362 } 363 364 plog.Log(1, fmt.Sprintf("height %d", height)) 365 366 // clear maps 367 for n := range opcount { 368 delete(opcount, n) 369 } 370 for n := range epcount { 371 delete(epcount, n) 372 } 373 374 // count operations and details 375 for _, v := range b.Operations { 376 for _, vv := range v { 377 for _, op := range vv.Contents { 378 kind := op.Kind() 379 if c, ok := opcount[kind]; ok { 380 opcount[kind] = c + 1 381 } else { 382 opcount[kind] = 1 383 } 384 if kind == mavryk.OpTypeTransaction { 385 tx := op.(*rpc.Transaction) 386 if tx.Parameters != nil { 387 epcount[tx.Parameters.Entrypoint]++ 388 } 389 for _, vvv := range tx.Metadata.InternalResults { 390 kind = vvv.Kind 391 opcount[kind]++ 392 } 393 } 394 } 395 } 396 } 397 for _, op := range oplist { 398 if n, ok := opcount[op]; ok { 399 if len(eplist) > 0 { 400 for _, ep := range eplist { 401 if n, ok := epcount[ep]; ok { 402 fmt.Printf("%s level=%d contains %d %s(s)\n", b.Hash, b.GetLevel(), n, ep) 403 } 404 // output relevant ops 405 if !verbose { 406 continue 407 } 408 for _, v := range b.Operations { 409 for _, vv := range v { 410 for _, o := range vv.Contents { 411 if op == o.Kind() && o.(*rpc.Transaction).Parameters != nil && o.(*rpc.Transaction).Parameters.Entrypoint == ep { 412 enc.Encode(o) 413 } 414 } 415 } 416 } 417 } 418 } else { 419 fmt.Printf("%s level=%d contains %d %s(s)\n", b.Hash, b.GetLevel(), n, op) 420 // output relevant ops 421 if !verbose { 422 continue 423 } 424 for _, v := range b.Operations { 425 for _, vv := range v { 426 for _, o := range vv.Contents { 427 if op == o.Kind() { 428 enc.Encode(o) 429 } 430 } 431 } 432 } 433 } 434 } 435 } 436 height++ 437 438 // the tip has probably advanced a lot since first fetch above, 439 // but this is only for illustration 440 if height > tip.GetLevel() { 441 break 442 } 443 } 444 return nil 445 } 446 447 func searchDeactivations(ctx context.Context, c *rpc.Client, start int64) error { 448 // find the current blockchain tip 449 tips, err := c.GetTips(ctx, 1, mavryk.BlockHash{}) 450 if err != nil { 451 return err 452 } 453 if len(tips) == 0 || len(tips[0]) == 0 { 454 return fmt.Errorf("invalid chain tip") 455 } 456 tip, err := c.GetBlock(ctx, tips[0][0]) 457 if err != nil { 458 return err 459 } 460 461 // fetching blocks forward 462 height := start 463 enc := json.NewEncoder(os.Stdout) 464 enc.SetIndent("", " ") 465 for { 466 b, err := c.GetBlockHeight(ctx, height) 467 if err != nil { 468 return err 469 } 470 471 if b.Metadata.Level.Level%1000 == 0 { 472 fmt.Printf("Scanning blockchain at level %d\n", b.Metadata.Level.Level) 473 } 474 475 if len(b.Metadata.Deactivated) > 0 { 476 res := map[int64][]mavryk.Address{ 477 height: b.Metadata.Deactivated, 478 } 479 enc.Encode(res) 480 } 481 482 height++ 483 484 // the tip has probably advanced a lot since first fetch above, 485 // but this is only for illustration 486 if height > tip.Metadata.Level.Level { 487 break 488 } 489 } 490 return nil 491 } 492 493 func showContractInfo(ctx context.Context, c *rpc.Client, addr mavryk.Address) error { 494 fmt.Printf("Loading data for contract %s. This may take a while. Abort with Ctrl-C.\n", addr) 495 script, err := c.GetContractScript(ctx, addr) 496 if err != nil { 497 return err 498 } 499 500 // unfold Micheline storage into human-readable form 501 val := micheline.NewValue(script.StorageType(), script.Storage) 502 m, err := val.Map() 503 if err != nil { 504 return err 505 } 506 buf, err := json.MarshalIndent(m, " ", " ") 507 if err != nil { 508 return err 509 } 510 fmt.Printf("Storage:\n %v\n", string(buf)) 511 512 // identify bigmaps owned by the contract from contract type and storage 513 bm := script.Bigmaps() 514 if len(bm) == 0 { 515 fmt.Printf("Bigmaps (none)\n") 516 } else { 517 fmt.Printf("Bigmaps:\n") 518 } 519 for name, bigid := range bm { 520 // load bigmap type 521 biginfo, err := c.GetBigmapInfo(ctx, bigid, rpc.Head) 522 if err != nil { 523 return err 524 } 525 // list all bigmap keys 526 bigkeys, err := c.ListBigmapKeys(ctx, bigid, rpc.Head) 527 if err != nil { 528 return err 529 } 530 fmt.Printf(" %-15s id=%d n_keys=%d bytes=%d\n", name, bigid, len(bigkeys), biginfo.TotalBytes) 531 if verbose { 532 for i, key := range bigkeys { 533 // visit each key 534 bigval, err := c.GetBigmapValue(ctx, bigid, key, rpc.Head) 535 if err != nil { 536 return err 537 } 538 // unfold Micheline type into human readable form 539 val := micheline.NewValue(micheline.NewType(biginfo.ValueType), bigval) 540 m, err := val.Map() 541 if err != nil { 542 return err 543 } 544 buf, err := json.MarshalIndent(m, " ", " ") 545 if err != nil { 546 return err 547 } 548 fmt.Printf(" %03d: %s\n", i, string(buf)) 549 } 550 } 551 } 552 return nil 553 } 554 555 func showOpInfo(ctx context.Context, c *rpc.Client, bh rpc.BlockID, list, pos int) error { 556 fmt.Printf("Loading op %s/%d/%d\n", bh, list, pos) 557 op, err := c.GetBlockOperation(ctx, bh, list, pos) 558 if err != nil { 559 return err 560 } 561 fmt.Printf("Hash %s\n", op.Hash) 562 fmt.Printf("Parts %d\n", len(op.Contents)) 563 for i, o := range op.Contents { 564 fmt.Printf("Part %d\n", i+1) 565 fmt.Printf(" Type %s\n", o.Kind()) 566 switch o.Kind() { 567 case mavryk.OpTypeTransaction: 568 tx := o.(*rpc.Transaction) 569 fmt.Printf(" Dest %s\n", tx.Destination) 570 fmt.Printf(" Fee %d\n", tx.Fee) 571 fmt.Printf(" Counter %d\n", tx.Counter) 572 fmt.Printf(" Amount %d\n", tx.Amount) 573 fmt.Printf(" Gas %d/%d\n", tx.Metadata.Result.ConsumedGas, tx.GasLimit) 574 fmt.Printf(" Storage %d/%d\n", tx.Metadata.Result.PaidStorageSizeDiff, tx.StorageLimit) 575 fmt.Printf(" Internal %d (not shown)\n", len(tx.Metadata.InternalResults)) 576 var script *micheline.Script 577 if tx.Destination.IsContract() { 578 script, err = c.GetContractScript(ctx, tx.Destination) 579 if err != nil { 580 return err 581 } 582 } 583 if tx.Parameters != nil { 584 fmt.Printf(" Entrypoint %s\n", tx.Parameters.Entrypoint) 585 ep, _, err := tx.Parameters.MapEntrypoint(script.ParamType()) 586 if err != nil { 587 return err 588 } 589 val := micheline.NewValue(ep.Type(), tx.Parameters.Value) 590 m, err := val.Map() 591 if err != nil { 592 return err 593 } 594 buf, err := json.MarshalIndent(m, " ", " ") 595 if err != nil { 596 return err 597 } 598 fmt.Println(" " + string(buf)) 599 } 600 if prim := tx.Metadata.Result.Storage; prim != nil { 601 val := micheline.NewValue(script.StorageType(), *prim) 602 m, err := val.Map() 603 if err != nil { 604 return err 605 } 606 buf, err := json.MarshalIndent(m, " ", " ") 607 if err != nil { 608 return err 609 } 610 fmt.Printf("Storage:\n %s\n", string(buf)) 611 } 612 } 613 } 614 615 return nil 616 } 617 618 func showOpCost(ctx context.Context, c *rpc.Client, bh rpc.BlockID, list, pos int) error { 619 fmt.Printf("Loading op %s/%d/%d\n", bh, list, pos) 620 op, err := c.GetBlockOperation(ctx, bh, list, pos) 621 if err != nil { 622 return err 623 } 624 fmt.Printf("Hash %s\n", op.Hash) 625 total := op.TotalCosts() 626 fmt.Printf("Totals\n") 627 fmt.Printf(" Fee %d\n", total.Fee) 628 fmt.Printf(" Burn %d\n", total.Burn) 629 fmt.Printf(" GasUsed %d\n", total.GasUsed) 630 fmt.Printf(" StorageUsed %d\n", total.StorageUsed) 631 fmt.Printf(" StorageBurn %d\n", total.StorageBurn) 632 fmt.Printf(" AllocationBurn %d\n", total.AllocationBurn) 633 634 for i, o := range op.Contents { 635 limits := o.Limits() 636 costs := o.Costs() 637 fmt.Printf("Op %d -----------------\n", i+1) 638 fmt.Printf(" Type %s\n", o.Kind()) 639 fmt.Printf(" Gas %d/%d\n", costs.GasUsed, limits.GasLimit) 640 fmt.Printf(" Storage %d/%d\n", costs.StorageUsed, limits.StorageLimit) 641 fmt.Printf(" StorageBurn %d\n", costs.StorageBurn) 642 fmt.Printf(" AllocationBurn %d\n", costs.AllocationBurn) 643 644 switch o.Kind() { 645 case mavryk.OpTypeTransaction: 646 tx := o.(*rpc.Transaction) 647 for j, ir := range tx.Metadata.InternalResults { 648 costs := ir.Costs() 649 fmt.Printf("Internal %d -----------------\n", j+1) 650 fmt.Printf(" Type %s\n", ir.Kind) 651 fmt.Printf(" Gas %d\n", costs.GasUsed) 652 fmt.Printf(" Storage %d\n", costs.StorageUsed) 653 fmt.Printf(" StorageBurn %d\n", costs.StorageBurn) 654 fmt.Printf(" AllocationBurn %d\n", costs.AllocationBurn) 655 } 656 } 657 } 658 659 return nil 660 }