github.com/evdatsion/aphelion-dpos-bft@v0.32.1/consensus/wal_generator.go (about)

     1  package consensus
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"fmt"
     7  	"io"
     8  	"path/filepath"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/pkg/errors"
    13  
    14  	"github.com/evdatsion/aphelion-dpos-bft/abci/example/kvstore"
    15  	bc "github.com/evdatsion/aphelion-dpos-bft/blockchain"
    16  	cfg "github.com/evdatsion/aphelion-dpos-bft/config"
    17  	cmn "github.com/evdatsion/aphelion-dpos-bft/libs/common"
    18  	"github.com/evdatsion/aphelion-dpos-bft/libs/db"
    19  	"github.com/evdatsion/aphelion-dpos-bft/libs/log"
    20  	"github.com/evdatsion/aphelion-dpos-bft/mock"
    21  	"github.com/evdatsion/aphelion-dpos-bft/privval"
    22  	"github.com/evdatsion/aphelion-dpos-bft/proxy"
    23  	sm "github.com/evdatsion/aphelion-dpos-bft/state"
    24  	"github.com/evdatsion/aphelion-dpos-bft/types"
    25  )
    26  
    27  // WALGenerateNBlocks generates a consensus WAL. It does this by spinning up a
    28  // stripped down version of node (proxy app, event bus, consensus state) with a
    29  // persistent kvstore application and special consensus wal instance
    30  // (byteBufferWAL) and waits until numBlocks are created. If the node fails to produce given numBlocks, it returns an error.
    31  func WALGenerateNBlocks(t *testing.T, wr io.Writer, numBlocks int) (err error) {
    32  	config := getConfig(t)
    33  
    34  	app := kvstore.NewPersistentKVStoreApplication(filepath.Join(config.DBDir(), "wal_generator"))
    35  
    36  	logger := log.TestingLogger().With("wal_generator", "wal_generator")
    37  	logger.Info("generating WAL (last height msg excluded)", "numBlocks", numBlocks)
    38  
    39  	/////////////////////////////////////////////////////////////////////////////
    40  	// COPY PASTE FROM node.go WITH A FEW MODIFICATIONS
    41  	// NOTE: we can't import node package because of circular dependency.
    42  	// NOTE: we don't do handshake so need to set state.Version.Consensus.App directly.
    43  	privValidatorKeyFile := config.PrivValidatorKeyFile()
    44  	privValidatorStateFile := config.PrivValidatorStateFile()
    45  	privValidator := privval.LoadOrGenFilePV(privValidatorKeyFile, privValidatorStateFile)
    46  	genDoc, err := types.GenesisDocFromFile(config.GenesisFile())
    47  	if err != nil {
    48  		return errors.Wrap(err, "failed to read genesis file")
    49  	}
    50  	blockStoreDB := db.NewMemDB()
    51  	stateDB := blockStoreDB
    52  	state, err := sm.MakeGenesisState(genDoc)
    53  	if err != nil {
    54  		return errors.Wrap(err, "failed to make genesis state")
    55  	}
    56  	state.Version.Consensus.App = kvstore.ProtocolVersion
    57  	sm.SaveState(stateDB, state)
    58  	blockStore := bc.NewBlockStore(blockStoreDB)
    59  	proxyApp := proxy.NewAppConns(proxy.NewLocalClientCreator(app))
    60  	proxyApp.SetLogger(logger.With("module", "proxy"))
    61  	if err := proxyApp.Start(); err != nil {
    62  		return errors.Wrap(err, "failed to start proxy app connections")
    63  	}
    64  	defer proxyApp.Stop()
    65  
    66  	eventBus := types.NewEventBus()
    67  	eventBus.SetLogger(logger.With("module", "events"))
    68  	if err := eventBus.Start(); err != nil {
    69  		return errors.Wrap(err, "failed to start event bus")
    70  	}
    71  	defer eventBus.Stop()
    72  	mempool := mock.Mempool{}
    73  	evpool := sm.MockEvidencePool{}
    74  	blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), proxyApp.Consensus(), mempool, evpool)
    75  	consensusState := NewConsensusState(config.Consensus, state.Copy(), blockExec, blockStore, mempool, evpool)
    76  	consensusState.SetLogger(logger)
    77  	consensusState.SetEventBus(eventBus)
    78  	if privValidator != nil {
    79  		consensusState.SetPrivValidator(privValidator)
    80  	}
    81  	// END OF COPY PASTE
    82  	/////////////////////////////////////////////////////////////////////////////
    83  
    84  	// set consensus wal to buffered WAL, which will write all incoming msgs to buffer
    85  	numBlocksWritten := make(chan struct{})
    86  	wal := newByteBufferWAL(logger, NewWALEncoder(wr), int64(numBlocks), numBlocksWritten)
    87  	// see wal.go#103
    88  	wal.Write(EndHeightMessage{0})
    89  	consensusState.wal = wal
    90  
    91  	if err := consensusState.Start(); err != nil {
    92  		return errors.Wrap(err, "failed to start consensus state")
    93  	}
    94  
    95  	select {
    96  	case <-numBlocksWritten:
    97  		consensusState.Stop()
    98  		return nil
    99  	case <-time.After(1 * time.Minute):
   100  		consensusState.Stop()
   101  		return fmt.Errorf("waited too long for tendermint to produce %d blocks (grep logs for `wal_generator`)", numBlocks)
   102  	}
   103  }
   104  
   105  //WALWithNBlocks returns a WAL content with numBlocks.
   106  func WALWithNBlocks(t *testing.T, numBlocks int) (data []byte, err error) {
   107  	var b bytes.Buffer
   108  	wr := bufio.NewWriter(&b)
   109  
   110  	if err := WALGenerateNBlocks(t, wr, numBlocks); err != nil {
   111  		return []byte{}, err
   112  	}
   113  
   114  	wr.Flush()
   115  	return b.Bytes(), nil
   116  }
   117  
   118  func randPort() int {
   119  	// returns between base and base + spread
   120  	base, spread := 20000, 20000
   121  	return base + cmn.RandIntn(spread)
   122  }
   123  
   124  func makeAddrs() (string, string, string) {
   125  	start := randPort()
   126  	return fmt.Sprintf("tcp://0.0.0.0:%d", start),
   127  		fmt.Sprintf("tcp://0.0.0.0:%d", start+1),
   128  		fmt.Sprintf("tcp://0.0.0.0:%d", start+2)
   129  }
   130  
   131  // getConfig returns a config for test cases
   132  func getConfig(t *testing.T) *cfg.Config {
   133  	c := cfg.ResetTestRoot(t.Name())
   134  
   135  	// and we use random ports to run in parallel
   136  	tm, rpc, grpc := makeAddrs()
   137  	c.P2P.ListenAddress = tm
   138  	c.RPC.ListenAddress = rpc
   139  	c.RPC.GRPCListenAddress = grpc
   140  	return c
   141  }
   142  
   143  // byteBufferWAL is a WAL which writes all msgs to a byte buffer. Writing stops
   144  // when the heightToStop is reached. Client will be notified via
   145  // signalWhenStopsTo channel.
   146  type byteBufferWAL struct {
   147  	enc               *WALEncoder
   148  	stopped           bool
   149  	heightToStop      int64
   150  	signalWhenStopsTo chan<- struct{}
   151  
   152  	logger log.Logger
   153  }
   154  
   155  // needed for determinism
   156  var fixedTime, _ = time.Parse(time.RFC3339, "2017-01-02T15:04:05Z")
   157  
   158  func newByteBufferWAL(logger log.Logger, enc *WALEncoder, nBlocks int64, signalStop chan<- struct{}) *byteBufferWAL {
   159  	return &byteBufferWAL{
   160  		enc:               enc,
   161  		heightToStop:      nBlocks,
   162  		signalWhenStopsTo: signalStop,
   163  		logger:            logger,
   164  	}
   165  }
   166  
   167  // Save writes message to the internal buffer except when heightToStop is
   168  // reached, in which case it will signal the caller via signalWhenStopsTo and
   169  // skip writing.
   170  func (w *byteBufferWAL) Write(m WALMessage) {
   171  	if w.stopped {
   172  		w.logger.Debug("WAL already stopped. Not writing message", "msg", m)
   173  		return
   174  	}
   175  
   176  	if endMsg, ok := m.(EndHeightMessage); ok {
   177  		w.logger.Debug("WAL write end height message", "height", endMsg.Height, "stopHeight", w.heightToStop)
   178  		if endMsg.Height == w.heightToStop {
   179  			w.logger.Debug("Stopping WAL at height", "height", endMsg.Height)
   180  			w.signalWhenStopsTo <- struct{}{}
   181  			w.stopped = true
   182  			return
   183  		}
   184  	}
   185  
   186  	w.logger.Debug("WAL Write Message", "msg", m)
   187  	err := w.enc.Encode(&TimedWALMessage{fixedTime, m})
   188  	if err != nil {
   189  		panic(fmt.Sprintf("failed to encode the msg %v", m))
   190  	}
   191  }
   192  
   193  func (w *byteBufferWAL) WriteSync(m WALMessage) {
   194  	w.Write(m)
   195  }
   196  
   197  func (w *byteBufferWAL) FlushAndSync() error { return nil }
   198  
   199  func (w *byteBufferWAL) SearchForEndHeight(height int64, options *WALSearchOptions) (rd io.ReadCloser, found bool, err error) {
   200  	return nil, false, nil
   201  }
   202  
   203  func (w *byteBufferWAL) Start() error { return nil }
   204  func (w *byteBufferWAL) Stop() error  { return nil }
   205  func (w *byteBufferWAL) Wait()        {}