github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/tm2/pkg/bft/consensus/wal_test.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/stretchr/testify/assert"
    13  	"github.com/stretchr/testify/require"
    14  
    15  	auto "github.com/gnolang/gno/tm2/pkg/autofile"
    16  	"github.com/gnolang/gno/tm2/pkg/bft/abci/example/kvstore"
    17  	"github.com/gnolang/gno/tm2/pkg/bft/appconn"
    18  	"github.com/gnolang/gno/tm2/pkg/bft/mempool/mock"
    19  	"github.com/gnolang/gno/tm2/pkg/bft/privval"
    20  	"github.com/gnolang/gno/tm2/pkg/bft/proxy"
    21  	sm "github.com/gnolang/gno/tm2/pkg/bft/state"
    22  	"github.com/gnolang/gno/tm2/pkg/bft/store"
    23  	"github.com/gnolang/gno/tm2/pkg/bft/types"
    24  	walm "github.com/gnolang/gno/tm2/pkg/bft/wal"
    25  	"github.com/gnolang/gno/tm2/pkg/db/memdb"
    26  	"github.com/gnolang/gno/tm2/pkg/errors"
    27  	"github.com/gnolang/gno/tm2/pkg/events"
    28  	"github.com/gnolang/gno/tm2/pkg/log"
    29  )
    30  
    31  // ----------------------------------------
    32  // copied over from wal/wal_test.go
    33  
    34  const maxTestMsgSize int64 = 64 * 1024
    35  
    36  func makeTempWAL(t *testing.T, walChunkSize int64) (wal walm.WAL) {
    37  	t.Helper()
    38  
    39  	// Create WAL file.
    40  	walFile := filepath.Join(t.TempDir(), "wal")
    41  
    42  	// Create WAL.
    43  	wal, err := walm.NewWAL(walFile, maxTestMsgSize, auto.GroupHeadSizeLimit(walChunkSize))
    44  	if err != nil {
    45  		panic(err)
    46  	}
    47  	err = wal.Start()
    48  	if err != nil {
    49  		panic(err)
    50  	}
    51  
    52  	t.Cleanup(func() {
    53  		// WAL cleanup.
    54  		wal.Stop()
    55  		// wait for the wal to finish shutting down so we
    56  		// can safely remove the directory
    57  		wal.Wait()
    58  	})
    59  
    60  	return wal
    61  }
    62  
    63  // end copy from wal/wal_test.go
    64  // ----------------------------------------
    65  
    66  func TestWALTruncate(t *testing.T) {
    67  	t.Parallel()
    68  
    69  	const walChunkSize = 409610 // 4KB
    70  	wal := makeTempWAL(t, walChunkSize)
    71  
    72  	wal.SetLogger(log.NewTestingLogger(t))
    73  
    74  	type grouper interface {
    75  		Group() *auto.Group
    76  	}
    77  
    78  	// 60 block's size nearly 70K, greater than group's wal chunk filesize (4KB).
    79  	// When the headBuf is full, content will flush to the filesystem.
    80  	err := WALGenerateNBlocks(t, wal.(grouper).Group(), 60)
    81  	require.NoError(t, err)
    82  
    83  	time.Sleep(1 * time.Millisecond) // wait groupCheckDuration, make sure RotateFile run
    84  
    85  	wal.FlushAndSync()
    86  
    87  	h := int64(50)
    88  	gr, found, err := wal.SearchForHeight(h+1, &walm.WALSearchOptions{})
    89  	assert.NoError(t, err, "expected not to err on height %d", h)
    90  	assert.True(t, found, "expected to find end height for %d", h)
    91  	assert.NotNil(t, gr)
    92  	defer gr.Close()
    93  
    94  	dec := walm.NewWALReader(gr, maxMsgSize)
    95  	msg, meta, err := dec.ReadMessage()
    96  	assert.NoError(t, err, "expected to decode a message")
    97  	rs, ok := msg.Msg.(newRoundStepInfo)
    98  	assert.Nil(t, meta, "expected no meta")
    99  	assert.True(t, ok, "expected message of type EventRoundState")
   100  	assert.Equal(t, rs.Height, h+1, "wrong height")
   101  }
   102  
   103  // XXX: WALGenerateNBlocks and WALWithNBlocks were removed from wal_generator.go
   104  // as they are unused.
   105  // If you intend to use them, please move them to a separate package.
   106  
   107  // WALGenerateNBlocks generates a consensus WAL. It does this by spinning up a
   108  // stripped down version of node (proxy app, event bus, consensus state) with a
   109  // persistent kvstore application and special consensus wal instance
   110  // (heightStopWAL) and waits until numBlocks are created. If the node fails to produce given numBlocks, it returns an error.
   111  func WALGenerateNBlocks(t *testing.T, wr io.Writer, numBlocks int) (err error) {
   112  	t.Helper()
   113  
   114  	config, genesisFile := getConfig(t)
   115  
   116  	app := kvstore.NewPersistentKVStoreApplication(filepath.Join(config.DBDir(), "wal_generator"))
   117  	defer app.Close()
   118  
   119  	logger := log.NewNoopLogger().With("wal_generator", "wal_generator")
   120  	logger.Info("generating WAL (last height msg excluded)", "numBlocks", numBlocks)
   121  
   122  	// -----------
   123  	// COPY PASTE FROM node.go WITH A FEW MODIFICATIONS
   124  	// NOTE: we can't import node package because of circular dependency.
   125  	// NOTE: we don't do handshake so need to set state.Version.Consensus.App directly.
   126  	privValidatorKeyFile := config.PrivValidatorKeyFile()
   127  	privValidatorStateFile := config.PrivValidatorStateFile()
   128  	privValidator := privval.LoadOrGenFilePV(privValidatorKeyFile, privValidatorStateFile)
   129  	genDoc, err := types.GenesisDocFromFile(genesisFile)
   130  	if err != nil {
   131  		return errors.Wrap(err, "failed to read genesis file")
   132  	}
   133  	blockStoreDB := memdb.NewMemDB()
   134  	stateDB := blockStoreDB
   135  	state, err := sm.MakeGenesisState(genDoc)
   136  	if err != nil {
   137  		return errors.Wrap(err, "failed to make genesis state")
   138  	}
   139  	state.AppVersion = kvstore.AppVersion
   140  	sm.SaveState(stateDB, state)
   141  	blockStore := store.NewBlockStore(blockStoreDB)
   142  
   143  	proxyApp := appconn.NewAppConns(proxy.NewLocalClientCreator(app))
   144  	proxyApp.SetLogger(logger.With("module", "proxy"))
   145  	if err := proxyApp.Start(); err != nil {
   146  		return errors.Wrap(err, "failed to start proxy app connections")
   147  	}
   148  	defer proxyApp.Stop()
   149  
   150  	evsw := events.NewEventSwitch()
   151  	evsw.SetLogger(logger.With("module", "events"))
   152  	if err := evsw.Start(); err != nil {
   153  		return errors.Wrap(err, "failed to start event bus")
   154  	}
   155  	defer evsw.Stop()
   156  	mempool := mock.Mempool{}
   157  	blockExec := sm.NewBlockExecutor(stateDB, log.NewNoopLogger(), proxyApp.Consensus(), mempool)
   158  	consensusState := NewConsensusState(config.Consensus, state.Copy(), blockExec, blockStore, mempool)
   159  	consensusState.SetLogger(logger)
   160  	consensusState.SetEventSwitch(evsw)
   161  	if privValidator != nil {
   162  		consensusState.SetPrivValidator(privValidator)
   163  	}
   164  	// END OF COPY PASTE
   165  	// -----------
   166  
   167  	// set consensus wal to buffered WAL, which will write all incoming msgs to buffer
   168  	numBlocksWritten := make(chan struct{})
   169  	wal := newHeightStopWAL(logger, walm.NewWALWriter(wr, maxMsgSize), int64(numBlocks)+1, numBlocksWritten)
   170  	// See wal.go OnStart().
   171  	// Since we separate the WALWriter from the WAL, we need to
   172  	// initialize ourself.
   173  	wal.WriteMetaSync(walm.MetaMessage{Height: 1})
   174  	consensusState.wal = wal
   175  
   176  	if err := consensusState.Start(); err != nil {
   177  		return errors.Wrap(err, "failed to start consensus state")
   178  	}
   179  
   180  	select {
   181  	case <-numBlocksWritten:
   182  		consensusState.Stop()
   183  		return nil
   184  	case <-time.After(2 * time.Minute):
   185  		consensusState.Stop()
   186  		return fmt.Errorf("waited too long for tendermint to produce %d blocks (grep logs for `wal_generator`)", numBlocks)
   187  	}
   188  }
   189  
   190  // WALWithNBlocks returns a WAL content with numBlocks.
   191  func WALWithNBlocks(t *testing.T, numBlocks int) (data []byte, err error) {
   192  	t.Helper()
   193  
   194  	var b bytes.Buffer
   195  	wr := bufio.NewWriter(&b)
   196  
   197  	if err := WALGenerateNBlocks(t, wr, numBlocks); err != nil {
   198  		return []byte{}, err
   199  	}
   200  
   201  	wr.Flush()
   202  	return b.Bytes(), nil
   203  }