- package consensus
-
- import (
- "bufio"
- "bytes"
- "context"
- "fmt"
- "io"
- mrand "math/rand"
- "path/filepath"
- "testing"
- "time"
-
- "github.com/stretchr/testify/require"
- dbm "github.com/tendermint/tm-db"
-
- abciclient "github.com/tendermint/tendermint/abci/client"
- "github.com/tendermint/tendermint/abci/example/kvstore"
- "github.com/tendermint/tendermint/config"
- "github.com/tendermint/tendermint/internal/eventbus"
- "github.com/tendermint/tendermint/internal/proxy"
- sm "github.com/tendermint/tendermint/internal/state"
- "github.com/tendermint/tendermint/internal/store"
- "github.com/tendermint/tendermint/libs/log"
- "github.com/tendermint/tendermint/privval"
- "github.com/tendermint/tendermint/types"
- )
-
- // WALGenerateNBlocks generates a consensus WAL. It does this by spinning up a
- // stripped down version of node (proxy app, event bus, consensus state) with a
- // persistent kvstore application and special consensus wal instance
- // (byteBufferWAL) and waits until numBlocks are created.
- // If the node fails to produce given numBlocks, it returns an error.
- func WALGenerateNBlocks(ctx context.Context, t *testing.T, logger log.Logger, wr io.Writer, numBlocks int) (err error) {
- cfg := getConfig(t)
-
- app := kvstore.NewPersistentKVStoreApplication(logger, filepath.Join(cfg.DBDir(), "wal_generator"))
- t.Cleanup(func() { require.NoError(t, app.Close()) })
-
- logger.Info("generating WAL (last height msg excluded)", "numBlocks", numBlocks)
-
- // COPY PASTE FROM node.go WITH A FEW MODIFICATIONS
- // NOTE: we can't import node package because of circular dependency.
- // NOTE: we don't do handshake so need to set state.Version.Consensus.App directly.
- privValidatorKeyFile := cfg.PrivValidator.KeyFile()
- privValidatorStateFile := cfg.PrivValidator.StateFile()
- privValidator, err := privval.LoadOrGenFilePV(privValidatorKeyFile, privValidatorStateFile)
- if err != nil {
- return err
- }
- genDoc, err := types.GenesisDocFromFile(cfg.GenesisFile())
- if err != nil {
- return fmt.Errorf("failed to read genesis file: %w", err)
- }
- blockStoreDB := dbm.NewMemDB()
- stateDB := blockStoreDB
- stateStore := sm.NewStore(stateDB)
- state, err := sm.MakeGenesisState(genDoc)
- if err != nil {
- return fmt.Errorf("failed to make genesis state: %w", err)
- }
- state.Version.Consensus.App = kvstore.ProtocolVersion
- if err = stateStore.Save(state); err != nil {
- t.Error(err)
- }
-
- blockStore := store.NewBlockStore(blockStoreDB)
-
- proxyApp := proxy.NewAppConns(abciclient.NewLocalCreator(app), logger.With("module", "proxy"), proxy.NopMetrics())
- if err := proxyApp.Start(ctx); err != nil {
- return fmt.Errorf("failed to start proxy app connections: %w", err)
- }
-
- eventBus := eventbus.NewDefault(logger.With("module", "events"))
- if err := eventBus.Start(ctx); err != nil {
- return fmt.Errorf("failed to start event bus: %w", err)
- }
-
- mempool := emptyMempool{}
- evpool := sm.EmptyEvidencePool{}
- blockExec := sm.NewBlockExecutor(stateStore, log.TestingLogger(), proxyApp.Consensus(), mempool, evpool, blockStore)
- consensusState := NewState(ctx, logger, cfg.Consensus, state.Copy(), blockExec, blockStore, mempool, evpool)
- consensusState.SetEventBus(eventBus)
- if privValidator != nil && privValidator != (*privval.FilePV)(nil) {
- consensusState.SetPrivValidator(ctx, privValidator)
- }
- // END OF COPY PASTE
-
- // set consensus wal to buffered WAL, which will write all incoming msgs to buffer
- numBlocksWritten := make(chan struct{})
- wal := newByteBufferWAL(logger, NewWALEncoder(wr), int64(numBlocks), numBlocksWritten)
- // see wal.go#103
- if err := wal.Write(EndHeightMessage{0}); err != nil {
- t.Error(err)
- }
-
- consensusState.wal = wal
-
- if err := consensusState.Start(ctx); err != nil {
- return fmt.Errorf("failed to start consensus state: %w", err)
- }
-
- select {
- case <-numBlocksWritten:
- if err := consensusState.Stop(); err != nil {
- t.Error(err)
- }
- return nil
- case <-time.After(1 * time.Minute):
- if err := consensusState.Stop(); err != nil {
- t.Error(err)
- }
- return fmt.Errorf("waited too long for tendermint to produce %d blocks (grep logs for `wal_generator`)", numBlocks)
- }
- }
-
- // WALWithNBlocks returns a WAL content with numBlocks.
- func WALWithNBlocks(ctx context.Context, t *testing.T, logger log.Logger, numBlocks int) (data []byte, err error) {
- var b bytes.Buffer
- wr := bufio.NewWriter(&b)
-
- if err := WALGenerateNBlocks(ctx, t, logger, wr, numBlocks); err != nil {
- return []byte{}, err
- }
-
- wr.Flush()
- return b.Bytes(), nil
- }
-
- func randPort() int {
- // returns between base and base + spread
- base, spread := 20000, 20000
- // nolint:gosec // G404: Use of weak random number generator
- return base + mrand.Intn(spread)
- }
-
- // makeAddrs constructs local TCP addresses for node services.
- // It uses consecutive ports from a random starting point, so that concurrent
- // instances are less likely to collide.
- func makeAddrs() (p2pAddr, rpcAddr string) {
- const addrTemplate = "tcp://127.0.0.1:%d"
- start := randPort()
- return fmt.Sprintf(addrTemplate, start), fmt.Sprintf(addrTemplate, start+1)
- }
-
- // getConfig returns a config for test cases
- func getConfig(t *testing.T) *config.Config {
- c, err := config.ResetTestRoot(t.TempDir(), t.Name())
- require.NoError(t, err)
-
- p2pAddr, rpcAddr := makeAddrs()
- c.P2P.ListenAddress = p2pAddr
- c.RPC.ListenAddress = rpcAddr
- return c
- }
-
- // byteBufferWAL is a WAL which writes all msgs to a byte buffer. Writing stops
- // when the heightToStop is reached. Client will be notified via
- // signalWhenStopsTo channel.
- type byteBufferWAL struct {
- enc *WALEncoder
- stopped bool
- heightToStop int64
- signalWhenStopsTo chan<- struct{}
-
- logger log.Logger
- }
-
- // needed for determinism
- var fixedTime, _ = time.Parse(time.RFC3339, "2017-01-02T15:04:05Z")
-
- func newByteBufferWAL(logger log.Logger, enc *WALEncoder, nBlocks int64, signalStop chan<- struct{}) *byteBufferWAL {
- return &byteBufferWAL{
- enc: enc,
- heightToStop: nBlocks,
- signalWhenStopsTo: signalStop,
- logger: logger,
- }
- }
-
- // Save writes message to the internal buffer except when heightToStop is
- // reached, in which case it will signal the caller via signalWhenStopsTo and
- // skip writing.
- func (w *byteBufferWAL) Write(m WALMessage) error {
- if w.stopped {
- w.logger.Debug("WAL already stopped. Not writing message", "msg", m)
- return nil
- }
-
- if endMsg, ok := m.(EndHeightMessage); ok {
- w.logger.Debug("WAL write end height message", "height", endMsg.Height, "stopHeight", w.heightToStop)
- if endMsg.Height == w.heightToStop {
- w.logger.Debug("Stopping WAL at height", "height", endMsg.Height)
- w.signalWhenStopsTo <- struct{}{}
- w.stopped = true
- return nil
- }
- }
-
- w.logger.Debug("WAL Write Message", "msg", m)
- err := w.enc.Encode(&TimedWALMessage{fixedTime, m})
- if err != nil {
- panic(fmt.Sprintf("failed to encode the msg %v", m))
- }
-
- return nil
- }
-
- func (w *byteBufferWAL) WriteSync(m WALMessage) error {
- return w.Write(m)
- }
-
- func (w *byteBufferWAL) FlushAndSync() error { return nil }
-
- func (w *byteBufferWAL) SearchForEndHeight(
- height int64,
- options *WALSearchOptions) (rd io.ReadCloser, found bool, err error) {
- return nil, false, nil
- }
-
- func (w *byteBufferWAL) Start(context.Context) error { return nil }
- func (w *byteBufferWAL) Stop() error { return nil }
- func (w *byteBufferWAL) Wait() {}
|