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  }