github.com/hyperledger/burrow@v0.34.5-0.20220512172541-77f09336001d/cmd/burrow/commands/explore.go (about)

     1  package commands
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"os"
     7  
     8  	"github.com/hyperledger/burrow/config"
     9  	"github.com/hyperledger/burrow/forensics"
    10  
    11  	"github.com/hyperledger/burrow/bcm"
    12  
    13  	"github.com/hyperledger/burrow/txs"
    14  	cli "github.com/jawher/mow.cli"
    15  	dbm "github.com/tendermint/tm-db"
    16  )
    17  
    18  // Explore chain state(s)
    19  func Explore(output Output) func(cmd *cli.Cmd) {
    20  	return func(cmd *cli.Cmd) {
    21  		configOpts := addConfigOptions(cmd)
    22  		var conf *config.BurrowConfig
    23  		var explorer *bcm.BlockStore
    24  		var err error
    25  
    26  		cmd.Before = func() {
    27  			conf, err = configOpts.obtainBurrowConfig()
    28  			if err != nil {
    29  				output.Fatalf("could not obtain config: %v", err)
    30  			}
    31  			tmConf, err := conf.TendermintConfig()
    32  			if err != nil {
    33  				output.Fatalf("could not build Tendermint config:", err)
    34  			}
    35  
    36  			if conf.GenesisDoc == nil {
    37  				output.Fatalf("genesis doc is required")
    38  			}
    39  
    40  			explorer, err = bcm.NewBlockExplorer(dbm.BackendType(tmConf.DBBackend), tmConf.DBDir())
    41  			if err != nil {
    42  				output.Fatalf("could not create BlockExplorer: %w", err)
    43  			}
    44  		}
    45  
    46  		cmd.Command("dump", "pretty print the state tree at the given height", func(cmd *cli.Cmd) {
    47  			heightOpt := cmd.IntOpt("height", 0, "The height to read, defaults to latest")
    48  			stateDir := cmd.StringArg("STATE", "", "Directory containing burrow state")
    49  			cmd.Spec = "[--height] [STATE]"
    50  
    51  			cmd.Before = func() {
    52  				if err := isDir(*stateDir); err != nil {
    53  					output.Fatalf("could not obtain state: %v", err)
    54  				}
    55  			}
    56  
    57  			cmd.Action = func() {
    58  				replay := forensics.NewSourceFromDir(conf.GenesisDoc, *stateDir)
    59  				height := uint64(*heightOpt)
    60  				if height == 0 {
    61  					height, err = replay.LatestHeight()
    62  					if err != nil {
    63  						output.Fatalf("could not read latest height: %v", err)
    64  					}
    65  				}
    66  				err := replay.LoadAt(height)
    67  				if err != nil {
    68  					output.Fatalf("could not load state: %v", err)
    69  				}
    70  
    71  				fmt.Println(replay.State.Dump())
    72  			}
    73  		})
    74  
    75  		cmd.Command("compare", "diff the state of two .burrow directories", func(cmd *cli.Cmd) {
    76  			goodDir := cmd.StringArg("GOOD", "", "Directory containing expected state")
    77  			badDir := cmd.StringArg("BAD", "", "Directory containing invalid state")
    78  			heightOpt := cmd.IntOpt("height", 0, "The height to read, defaults to latest")
    79  			cmd.Spec = "[--height] [GOOD] [BAD]"
    80  
    81  			cmd.Before = func() {
    82  				if err := isDir(*goodDir); err != nil {
    83  					output.Fatalf("could not obtain state: %v", err)
    84  				}
    85  				if err := isDir(*badDir); err != nil {
    86  					output.Fatalf("could not obtain state: %v", err)
    87  				}
    88  			}
    89  
    90  			cmd.Action = func() {
    91  				replay1 := forensics.NewReplay(
    92  					forensics.NewSourceFromDir(conf.GenesisDoc, *goodDir),
    93  					forensics.NewSourceFromGenesis(conf.GenesisDoc),
    94  				)
    95  				replay2 := forensics.NewReplay(
    96  					forensics.NewSourceFromDir(conf.GenesisDoc, *badDir),
    97  					forensics.NewSourceFromGenesis(conf.GenesisDoc),
    98  				)
    99  
   100  				h1, err := replay1.Src.LatestHeight()
   101  				if err != nil {
   102  					output.Fatalf("could not get height for first replay: %v", err)
   103  				}
   104  				h2, err := replay2.Src.LatestHeight()
   105  				if err != nil {
   106  					output.Fatalf("could not get height for second replay: %v", err)
   107  				}
   108  
   109  				height := h1
   110  				if *heightOpt != 0 {
   111  					height = uint64(*heightOpt)
   112  				} else if h2 < h1 {
   113  					height = h2
   114  					output.Printf("States do not agree on last height, using min: %d", h2)
   115  				} else {
   116  					output.Printf("Using default last height: %d", h1)
   117  				}
   118  
   119  				recap1, err := replay1.Blocks(1, height)
   120  				if err != nil {
   121  					output.Fatalf("could not replay first state: %v", err)
   122  				}
   123  
   124  				recap2, err := replay2.Blocks(1, height)
   125  				if err != nil {
   126  					output.Fatalf("could not replay second state: %v", err)
   127  				}
   128  
   129  				if height, err := forensics.CompareCaptures(recap1, recap2); err != nil {
   130  					output.Printf("difference in capture: %v", err)
   131  					// TODO: compare at every height?
   132  					if err := forensics.CompareStateAtHeight(replay1.Dst.State, replay2.Dst.State, height); err != nil {
   133  						output.Fatalf("difference in state: %v", err)
   134  					}
   135  				}
   136  
   137  				output.Printf("States match!")
   138  			}
   139  		})
   140  
   141  		cmd.Command("blocks", "dump blocks to stdout", func(cmd *cli.Cmd) {
   142  			rangeArg := cmd.StringArg("RANGE", "", "Range as START_HEIGHT:END_HEIGHT where omitting "+
   143  				"either endpoint implicitly describes the start/end and a negative index counts back from the last block")
   144  
   145  			cmd.Spec = "[RANGE]"
   146  
   147  			cmd.Action = func() {
   148  				start, end, err := parseRange(*rangeArg)
   149  				if err != nil {
   150  					output.Fatalf("could not parse range '%s': %v", *rangeArg, err)
   151  				}
   152  
   153  				err = explorer.Blocks(start, end,
   154  					func(block *bcm.Block) error {
   155  						bs, err := json.Marshal(block)
   156  						if err != nil {
   157  							output.Fatalf("Could not serialise block: %v", err)
   158  						}
   159  						output.Printf(string(bs))
   160  						return nil
   161  					})
   162  				if err != nil {
   163  					output.Fatalf("Error iterating over blocks: %v", err)
   164  				}
   165  			}
   166  		})
   167  
   168  		cmd.Command("txs", "dump transactions to stdout", func(cmd *cli.Cmd) {
   169  			rangeArg := cmd.StringArg("RANGE", "", "Range as START_HEIGHT:END_HEIGHT where omitting "+
   170  				"either endpoint implicitly describes the start/end and a negative index counts back from the last block")
   171  
   172  			cmd.Spec = "[RANGE]"
   173  
   174  			cmd.Action = func() {
   175  				start, end, err := parseRange(*rangeArg)
   176  				if err != nil {
   177  					output.Fatalf("could not parse range '%s': %v", *rangeArg, err)
   178  				}
   179  
   180  				err = explorer.Blocks(start, end,
   181  					func(block *bcm.Block) error {
   182  						err := block.Transactions(func(txEnv *txs.Envelope) error {
   183  							wrapper := struct {
   184  								Height int64
   185  								Tx     *txs.Envelope
   186  							}{
   187  								Height: block.Height,
   188  								Tx:     txEnv,
   189  							}
   190  							bs, err := json.Marshal(wrapper)
   191  							if err != nil {
   192  								output.Fatalf("Could not deserialise transaction: %v", err)
   193  							}
   194  							output.Printf(string(bs))
   195  							return nil
   196  						})
   197  						if err != nil {
   198  							output.Fatalf("Error iterating over transactions: %v", err)
   199  						}
   200  						// If we stopped transactions stop everything
   201  						return nil
   202  					})
   203  				if err != nil {
   204  					output.Fatalf("Error iterating over blocks: %v", err)
   205  				}
   206  			}
   207  		})
   208  	}
   209  }
   210  
   211  func isDir(path string) error {
   212  	file, err := os.Stat(path)
   213  	if err != nil {
   214  		return fmt.Errorf("could not read state directory: %v", err)
   215  	} else if !file.IsDir() {
   216  		return fmt.Errorf("%s is not a directory", path)
   217  	}
   218  	return nil
   219  }