github.com/hyperledger/burrow@v0.34.5-0.20220512172541-77f09336001d/forensics/replay.go (about) 1 // This package contains tools for examining, replaying, and debugging Tendermint-side and Burrow-side blockchain state. 2 // Some code is quick and dirty from particular investigations and some is better extracted, encapsulated and generalised. 3 // The sketchy code is included so that useful tools can be progressively put together as the generality of the types of 4 // forensic debugging needed in the wild are determined. 5 6 package forensics 7 8 import ( 9 "bytes" 10 "encoding/hex" 11 "fmt" 12 "path" 13 14 "github.com/fatih/color" 15 "github.com/hyperledger/burrow/bcm" 16 "github.com/hyperledger/burrow/binary" 17 "github.com/hyperledger/burrow/consensus/tendermint" 18 "github.com/hyperledger/burrow/core" 19 "github.com/hyperledger/burrow/event" 20 "github.com/hyperledger/burrow/execution" 21 "github.com/hyperledger/burrow/execution/state" 22 "github.com/hyperledger/burrow/forensics/storage" 23 "github.com/hyperledger/burrow/genesis" 24 "github.com/hyperledger/burrow/logging" 25 "github.com/hyperledger/burrow/txs" 26 "github.com/pkg/errors" 27 sm "github.com/tendermint/tendermint/state" 28 "github.com/tendermint/tendermint/store" 29 "github.com/tendermint/tendermint/types" 30 dbm "github.com/tendermint/tm-db" 31 "github.com/xlab/treeprint" 32 ) 33 34 // Source is a kernel for tracking state 35 type Source struct { 36 Explorer *bcm.BlockStore 37 State *state.State 38 db dbm.DB 39 cacheDB dbm.DB 40 blockchain *bcm.Blockchain 41 genesisDoc *genesis.GenesisDoc 42 committer execution.BatchCommitter 43 logger *logging.Logger 44 } 45 46 func NewSource(burrowDB, tmDB dbm.DB, genesisDoc *genesis.GenesisDoc) *Source { 47 // Avoid writing through to underlying DB 48 cacheDB := storage.NewCacheDB(burrowDB) 49 return &Source{ 50 Explorer: bcm.NewBlockStore(store.NewBlockStore(tmDB)), 51 db: burrowDB, 52 cacheDB: cacheDB, 53 blockchain: bcm.NewBlockchain(cacheDB, genesisDoc), 54 genesisDoc: genesisDoc, 55 logger: logging.NewNoopLogger(), 56 } 57 } 58 59 func NewSourceFromDir(genesisDoc *genesis.GenesisDoc, dbDir string) *Source { 60 burrowDB, err := dbm.NewDB(core.BurrowDBName, dbm.GoLevelDBBackend, dbDir) 61 if err != nil { 62 panic(fmt.Errorf("could not create core DB for replay source: %w", err)) 63 } 64 tmDB, err := dbm.NewDB("blockstore", dbm.GoLevelDBBackend, path.Join(dbDir, "data")) 65 if err != nil { 66 panic(fmt.Errorf("could not create blockstore DB for replay source: %w", err)) 67 } 68 return NewSource(burrowDB, tmDB, genesisDoc) 69 } 70 71 func NewSourceFromGenesis(genesisDoc *genesis.GenesisDoc) *Source { 72 tmDB := dbm.NewMemDB() 73 gd := tendermint.DeriveGenesisDoc(genesisDoc, nil) 74 st, err := sm.MakeGenesisState(&types.GenesisDoc{ 75 ChainID: gd.ChainID, 76 Validators: gd.Validators, 77 AppHash: gd.AppHash, 78 }) 79 if err != nil { 80 panic(err) 81 } 82 stateStore := sm.NewStore(tmDB) 83 err = stateStore.Save(st) 84 if err != nil { 85 panic(err) 86 } 87 burrowDB, burrowState, burrowChain, err := initBurrow(genesisDoc) 88 if err != nil { 89 panic(err) 90 } 91 src := NewSource(burrowDB, tmDB, genesisDoc) 92 src.State = burrowState 93 src.committer, err = execution.NewBatchCommitter(burrowState, execution.ParamsFromGenesis(genesisDoc), 94 burrowChain, event.NewEmitter(), logging.NewNoopLogger()) 95 if err != nil { 96 panic(err) 97 } 98 return src 99 } 100 101 func initBurrow(gd *genesis.GenesisDoc) (dbm.DB, *state.State, *bcm.Blockchain, error) { 102 db := dbm.NewMemDB() 103 st, err := state.MakeGenesisState(db, gd) 104 if err != nil { 105 return nil, nil, nil, err 106 } 107 err = st.InitialCommit() 108 if err != nil { 109 return nil, nil, nil, err 110 } 111 chain := bcm.NewBlockchain(db, gd) 112 return db, st, chain, nil 113 } 114 115 // LoadAt height 116 func (src *Source) LoadAt(height uint64) (err error) { 117 if height >= 1 { 118 // Load and commit previous block 119 block, err := src.Explorer.Block(int64(height)) 120 if err != nil { 121 return err 122 } 123 err = src.blockchain.CommitBlockAtHeight(block.Time, block.Hash(), block.Header.AppHash, uint64(block.Height)) 124 if err != nil { 125 return err 126 } 127 } 128 src.State, err = state.LoadState(src.cacheDB, execution.VersionAtHeight(height)) 129 if err != nil { 130 return err 131 } 132 133 // Get our commit machinery 134 src.committer, err = execution.NewBatchCommitter(src.State, execution.ParamsFromGenesis(src.genesisDoc), src.blockchain, 135 event.NewEmitter(), src.logger) 136 return err 137 } 138 139 func (src *Source) LatestHeight() (uint64, error) { 140 blockchain, _, err := bcm.LoadOrNewBlockchain(src.db, src.genesisDoc, src.logger) 141 if err != nil { 142 return 0, err 143 } 144 return blockchain.LastBlockHeight(), nil 145 } 146 147 func (src *Source) LatestBlockchain() (*bcm.Blockchain, error) { 148 blockchain, _, err := bcm.LoadOrNewBlockchain(src.db, src.genesisDoc, src.logger) 149 if err != nil { 150 return nil, err 151 } 152 src.blockchain = blockchain 153 return blockchain, nil 154 } 155 156 // Replay is a kernel for state replaying 157 type Replay struct { 158 Src *Source 159 Dst *Source 160 } 161 162 func NewReplay(src, dst *Source) *Replay { 163 return &Replay{src, dst} 164 } 165 166 // Block loads and commits a block 167 func (re *Replay) Block(height uint64) (*ReplayCapture, error) { 168 // block.AppHash is hash after txs from previous block have been applied - it's the state we want to load on top 169 // of which we will reapply this block txs 170 if err := re.Src.LoadAt(height - 1); err != nil { 171 return nil, err 172 } 173 return re.Commit(height) 174 } 175 176 // Blocks iterates through the given range 177 func (re *Replay) Blocks(startHeight, endHeight uint64) ([]*ReplayCapture, error) { 178 if err := re.Dst.LoadAt(startHeight - 1); err != nil { 179 return nil, errors.Wrap(err, "State()") 180 } 181 182 recaps := make([]*ReplayCapture, 0, endHeight-startHeight+1) 183 for height := startHeight; height < endHeight; height++ { 184 recap, err := re.Commit(height) 185 if err != nil { 186 return nil, err 187 } 188 recaps = append(recaps, recap) 189 } 190 return recaps, nil 191 } 192 193 // Commit block at height to state cache, saving a capture 194 func (re *Replay) Commit(height uint64) (*ReplayCapture, error) { 195 recap := &ReplayCapture{ 196 Height: height, 197 } 198 199 block, err := re.Src.Explorer.Block(int64(height)) 200 if err != nil { 201 return nil, errors.Wrap(err, "explorer.Block()") 202 } 203 if uint64(block.Height) != height { 204 return nil, errors.Errorf("Tendermint block height %d != requested block height %d", 205 block.Height, height) 206 } 207 if height > 1 && !bytes.Equal(re.Dst.State.Hash(), block.AppHash) { 208 return nil, errors.Errorf("state hash %X does not match AppHash %X at height %d", 209 re.Dst.State.Hash(), block.AppHash[:], height) 210 } 211 212 recap.AppHashBefore = binary.HexBytes(block.AppHash) 213 err = block.Transactions(func(txEnv *txs.Envelope) error { 214 txe, err := re.Dst.committer.Execute(txEnv) 215 if err != nil { 216 return errors.Wrap(err, "committer.Execute()") 217 } 218 recap.TxExecutions = append(recap.TxExecutions, txe) 219 return nil 220 }) 221 if err != nil { 222 return nil, errors.Wrap(err, "block.Transactions()") 223 } 224 225 abciHeader := types.TM2PB.Header(&block.Header) 226 recap.AppHashAfter, err = re.Dst.committer.Commit(&abciHeader) 227 if err != nil { 228 return nil, errors.Wrap(err, "committer.Commit()") 229 } 230 231 return recap, err 232 } 233 234 func iterComp(exp, act *state.ImmutableState, tree treeprint.Tree, prefix []byte) (uint, error) { 235 reader1, err := exp.Forest.Reader(prefix) 236 if err != nil { 237 return 0, err 238 } 239 240 reader2, err := act.Forest.Reader(prefix) 241 if err != nil { 242 return 0, err 243 } 244 245 var diffs uint 246 branch := tree.AddBranch(prefix) 247 return diffs, reader1.Iterate(nil, nil, true, 248 func(key, value []byte) error { 249 actual, err := reader2.Get(key) 250 if err != nil { 251 return err 252 } else if !bytes.Equal(actual, value) { 253 diffs++ 254 branch.AddNode(color.GreenString("%q -> %q", hex.EncodeToString(key), hex.EncodeToString(value))) 255 branch.AddNode(color.RedString("%q -> %q", hex.EncodeToString(key), hex.EncodeToString(actual))) 256 } 257 return nil 258 }) 259 } 260 261 // CompareStateAtHeight of two replays 262 func CompareStateAtHeight(exp, act *state.State, height uint64) error { 263 rs1, err := exp.AtHeight(height) 264 if err != nil { 265 return errors.Wrap(err, "could not load expected state") 266 } 267 rs2, err := act.AtHeight(height) 268 if err != nil { 269 return errors.Wrap(err, "could not load actual state") 270 } 271 272 var diffs uint 273 tree := treeprint.New() 274 275 for _, p := range state.Prefixes { 276 n, err := iterComp(rs1, rs2, tree, p) 277 if err != nil { 278 return err 279 } 280 diffs += n 281 } 282 283 if diffs > 0 { 284 return fmt.Errorf("found %d difference(s): \n%v", 285 diffs, tree.String()) 286 } 287 return nil 288 }