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  }