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 }