github.com/hyperledger/burrow@v0.34.5-0.20220512172541-77f09336001d/cmd/burrow/commands/dump.go (about) 1 package commands 2 3 import ( 4 "context" 5 "encoding/json" 6 "os" 7 "time" 8 9 "github.com/hyperledger/burrow/encoding" 10 11 "github.com/hyperledger/burrow/core" 12 "github.com/hyperledger/burrow/dump" 13 "github.com/hyperledger/burrow/logging/logconfig" 14 "github.com/hyperledger/burrow/rpc/rpcdump" 15 "github.com/hyperledger/burrow/rpc/rpcquery" 16 cli "github.com/jawher/mow.cli" 17 ) 18 19 type dumpOptions struct { 20 height *int 21 filename *string 22 useBinaryEncoding *bool 23 } 24 25 func maybeOutput(verbose *bool, output Output, format string, args ...interface{}) { 26 if verbose != nil && *verbose { 27 output.Logf(format, args) 28 } 29 } 30 31 func addDumpOptions(cmd *cli.Cmd, specOptions ...string) *dumpOptions { 32 cmd.Spec += "[--height=<state height to dump at>] [--binary]" 33 for _, spec := range specOptions { 34 cmd.Spec += " " + spec 35 } 36 cmd.Spec += "[FILE]" 37 return &dumpOptions{ 38 height: cmd.IntOpt("h height", 0, "Block height to dump to, defaults to latest block height"), 39 useBinaryEncoding: cmd.BoolOpt("b binary", false, "Output in binary encoding (default is JSON)"), 40 filename: cmd.StringArg("FILE", "", "Location to output dump, if no argument is given then this streams to STDOUT"), 41 } 42 } 43 44 // Dump saves the state from a remote chain 45 func Dump(output Output) func(cmd *cli.Cmd) { 46 return func(cmd *cli.Cmd) { 47 verbose := cmd.BoolOpt("v verbose", false, "Verbose debug information") 48 cmd.Spec += "[--verbose]" 49 50 cmd.Command("local", "create a dump from local Burrow directory", func(cmd *cli.Cmd) { 51 maybeOutput(verbose, output, "dumping from local Burrow dir") 52 configFileOpt := cmd.String(configFileOption) 53 genesisFileOpt := cmd.String(genesisFileOption) 54 55 dumpOpts := addDumpOptions(cmd, configFileSpec, genesisFileSpec) 56 57 cmd.Action = func() { 58 conf, err := obtainDefaultConfig(*configFileOpt, *genesisFileOpt) 59 if err != nil { 60 output.Fatalf("could not obtain config: %v", err) 61 } 62 63 kern, err := core.NewKernel(conf.BurrowDir) 64 if err != nil { 65 output.Fatalf("could not create burrow kernel: %v", err) 66 } 67 68 err = kern.LoadState(conf.GenesisDoc) 69 if err != nil { 70 output.Fatalf("could not load burrow state: %v", err) 71 } 72 73 // Include all logging by default 74 logger, err := logconfig.New().Logger() 75 if err != nil { 76 output.Fatalf("could not make logger: %v", err) 77 } 78 79 source := dump.NewDumper(kern.State, kern.Blockchain).WithLogger(logger). 80 Source(0, uint64(*dumpOpts.height), dump.All) 81 82 err = dumpToFile(*dumpOpts.filename, source, *dumpOpts.useBinaryEncoding) 83 if err != nil { 84 output.Fatalf("could not dump to file %s': %v", *dumpOpts.filename, err) 85 } 86 maybeOutput(verbose, output, "dump successfully written to '%s'", *dumpOpts.filename) 87 } 88 }) 89 90 cmd.Command("remote", "pull a dump from a remote Burrow node", func(cmd *cli.Cmd) { 91 chainURLOpt := cmd.StringOpt("c chain", "127.0.0.1:10997", "chain to be used in IP:PORT format") 92 timeoutOpt := cmd.IntOpt("t timeout", 0, "Timeout in seconds") 93 dumpOpts := addDumpOptions(cmd, "[--chain=<chain GRPC address>]", "[--timeout=<GRPC timeout seconds>]") 94 95 cmd.Action = func() { 96 maybeOutput(verbose, output, "dumping from remote chain at %s", *chainURLOpt) 97 98 ctx, cancel := context.WithCancel(context.Background()) 99 if *timeoutOpt != 0 { 100 timeout := time.Duration(*timeoutOpt) * time.Second 101 ctx, cancel = context.WithTimeout(context.Background(), timeout) 102 } 103 defer cancel() 104 105 conn, err := encoding.GRPCDialContext(ctx, *chainURLOpt) 106 if err != nil { 107 output.Fatalf("failed to connect: %v", err) 108 } 109 110 qCli := rpcquery.NewQueryClient(conn) 111 chainStatus, err := qCli.Status(context.Background(), &rpcquery.StatusParam{}) 112 if err != nil { 113 output.Logf("could not get chain status: %v", err) 114 } 115 stat, err := json.Marshal(chainStatus) 116 if err != nil { 117 output.Logf("failed to marshal: %v", err) 118 } 119 maybeOutput(verbose, output, "dumping from chain: %s", string(stat)) 120 121 dc := rpcdump.NewDumpClient(conn) 122 receiver, err := dc.GetDump(ctx, &rpcdump.GetDumpParam{Height: uint64(*dumpOpts.height)}) 123 if err != nil { 124 output.Fatalf("failed to retrieve dump: %v", err) 125 } 126 127 err = dumpToFile(*dumpOpts.filename, receiver, *dumpOpts.useBinaryEncoding) 128 if err != nil { 129 output.Fatalf("could not dump to file %s': %v", *dumpOpts.filename, err) 130 } 131 132 maybeOutput(verbose, output, "dump successfully written to '%s'", *dumpOpts.filename) 133 } 134 }) 135 } 136 } 137 138 func dumpToFile(filename string, source dump.Source, useBinaryEncoding bool) error { 139 var file *os.File 140 var err error 141 if filename == "" { 142 file = os.Stdout 143 } else { 144 file, err = os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) 145 if err != nil { 146 return err 147 } 148 } 149 150 // Receive 151 err = dump.Write(file, source, useBinaryEncoding, dump.All) 152 if err != nil { 153 return err 154 } 155 156 err = file.Close() 157 if err != nil { 158 return err 159 } 160 return nil 161 }