github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/tm2/pkg/bft/node/node_test.go (about)

     1  package node
     2  
     3  import (
     4  	"fmt"
     5  	"net"
     6  	"os"
     7  	"syscall"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/stretchr/testify/assert"
    12  	"github.com/stretchr/testify/require"
    13  
    14  	"github.com/gnolang/gno/tm2/pkg/bft/abci/example/kvstore"
    15  	"github.com/gnolang/gno/tm2/pkg/bft/appconn"
    16  	cfg "github.com/gnolang/gno/tm2/pkg/bft/config"
    17  	mempl "github.com/gnolang/gno/tm2/pkg/bft/mempool"
    18  	"github.com/gnolang/gno/tm2/pkg/bft/privval"
    19  	"github.com/gnolang/gno/tm2/pkg/bft/proxy"
    20  	sm "github.com/gnolang/gno/tm2/pkg/bft/state"
    21  	"github.com/gnolang/gno/tm2/pkg/bft/types"
    22  	tmtime "github.com/gnolang/gno/tm2/pkg/bft/types/time"
    23  	"github.com/gnolang/gno/tm2/pkg/crypto/ed25519"
    24  	dbm "github.com/gnolang/gno/tm2/pkg/db"
    25  	"github.com/gnolang/gno/tm2/pkg/db/memdb"
    26  	"github.com/gnolang/gno/tm2/pkg/events"
    27  	"github.com/gnolang/gno/tm2/pkg/log"
    28  	"github.com/gnolang/gno/tm2/pkg/p2p"
    29  	p2pmock "github.com/gnolang/gno/tm2/pkg/p2p/mock"
    30  	"github.com/gnolang/gno/tm2/pkg/random"
    31  )
    32  
    33  func TestNodeStartStop(t *testing.T) {
    34  	config, genesisFile := cfg.ResetTestRoot("node_node_test")
    35  	defer os.RemoveAll(config.RootDir)
    36  
    37  	// create & start node
    38  	n, err := DefaultNewNode(config, genesisFile, log.NewNoopLogger())
    39  	require.NoError(t, err)
    40  	err = n.Start()
    41  	require.NoError(t, err)
    42  
    43  	t.Logf("Started node %v", n.sw.NodeInfo())
    44  
    45  	// wait for the node to produce a block
    46  	blocksSub := events.SubscribeToEvent(n.EventSwitch(), "node_test", types.EventNewBlock{})
    47  	require.NoError(t, err)
    48  	select {
    49  	case _, ok := <-blocksSub:
    50  		if !ok {
    51  			t.Fatal("blocksSub was cancelled")
    52  		}
    53  	case <-time.After(10 * time.Second):
    54  		t.Fatal("timed out waiting for the node to produce a block")
    55  	}
    56  
    57  	// stop the node
    58  	go func() {
    59  		n.Stop()
    60  	}()
    61  
    62  	select {
    63  	case <-n.Quit():
    64  	case <-time.After(5 * time.Second):
    65  		pid := os.Getpid()
    66  		p, err := os.FindProcess(pid)
    67  		if err != nil {
    68  			panic(err)
    69  		}
    70  		err = p.Signal(syscall.SIGABRT)
    71  		fmt.Println(err)
    72  		t.Fatal("timed out waiting for shutdown")
    73  	}
    74  }
    75  
    76  func TestSplitAndTrimEmpty(t *testing.T) {
    77  	testCases := []struct {
    78  		s        string
    79  		sep      string
    80  		cutset   string
    81  		expected []string
    82  	}{
    83  		{"a,b,c", ",", " ", []string{"a", "b", "c"}},
    84  		{" a , b , c ", ",", " ", []string{"a", "b", "c"}},
    85  		{" a, b, c ", ",", " ", []string{"a", "b", "c"}},
    86  		{" a, ", ",", " ", []string{"a"}},
    87  		{"   ", ",", " ", []string{}},
    88  	}
    89  
    90  	for _, tc := range testCases {
    91  		assert.Equal(t, tc.expected, splitAndTrimEmpty(tc.s, tc.sep, tc.cutset), "%s", tc.s)
    92  	}
    93  }
    94  
    95  func TestNodeDelayedStart(t *testing.T) {
    96  	config, genesisFile := cfg.ResetTestRoot("node_delayed_start_test")
    97  	defer os.RemoveAll(config.RootDir)
    98  	now := tmtime.Now()
    99  
   100  	// create & start node
   101  	n, err := DefaultNewNode(config, genesisFile, log.NewTestingLogger(t))
   102  	n.GenesisDoc().GenesisTime = now.Add(2 * time.Second)
   103  	require.NoError(t, err)
   104  
   105  	err = n.Start()
   106  	require.NoError(t, err)
   107  	defer n.Stop()
   108  
   109  	startTime := tmtime.Now()
   110  	assert.Equal(t, true, startTime.After(n.GenesisDoc().GenesisTime))
   111  }
   112  
   113  func TestNodeReady(t *testing.T) {
   114  	config, genesisFile := cfg.ResetTestRoot("node_node_test")
   115  	defer os.RemoveAll(config.RootDir)
   116  
   117  	// Create & start node
   118  	n, err := DefaultNewNode(config, genesisFile, log.NewTestingLogger(t))
   119  	require.NoError(t, err)
   120  
   121  	// Assert that blockstore has zero block before waiting for the first block
   122  	require.Equal(t, int64(0), n.BlockStore().Height())
   123  
   124  	// Assert that first block signal is not alreay received by calling Ready
   125  	select {
   126  	case <-n.Ready():
   127  		require.FailNow(t, "first block signal should not be close before starting the node")
   128  	default: // ok
   129  	}
   130  
   131  	err = n.Start()
   132  	require.NoError(t, err)
   133  	defer n.Stop()
   134  
   135  	// Wait until the node is ready or timeout
   136  	select {
   137  	case <-time.After(time.Second):
   138  		require.FailNow(t, "timeout while waiting for first block signal")
   139  	case <-n.Ready(): // ready
   140  	}
   141  
   142  	// Check that blockstore have at last one block
   143  	require.GreaterOrEqual(t, n.BlockStore().Height(), int64(1))
   144  }
   145  
   146  func TestNodeSetAppVersion(t *testing.T) {
   147  	config, genesisFile := cfg.ResetTestRoot("node_app_version_test")
   148  	defer os.RemoveAll(config.RootDir)
   149  
   150  	// create & start node
   151  	n, err := DefaultNewNode(config, genesisFile, log.NewTestingLogger(t))
   152  	require.NoError(t, err)
   153  
   154  	// default config uses the kvstore app
   155  	appVersion := kvstore.AppVersion
   156  
   157  	// check version is set in state
   158  	state := sm.LoadState(n.stateDB)
   159  	assert.Equal(t, state.AppVersion, appVersion)
   160  
   161  	// check version is set in node info
   162  	appVersion2, ok := n.nodeInfo.VersionSet.Get("app")
   163  	assert.True(t, ok)
   164  	assert.Equal(t, appVersion2.Version, appVersion)
   165  }
   166  
   167  func TestNodeSetPrivValTCP(t *testing.T) {
   168  	addr := "tcp://" + testFreeAddr(t)
   169  
   170  	config, genesisFile := cfg.ResetTestRoot("node_priv_val_tcp_test")
   171  	defer os.RemoveAll(config.RootDir)
   172  	config.BaseConfig.PrivValidatorListenAddr = addr
   173  
   174  	dialer := privval.DialTCPFn(addr, 100*time.Millisecond, ed25519.GenPrivKey())
   175  	dialerEndpoint := privval.NewSignerDialerEndpoint(
   176  		log.NewTestingLogger(t),
   177  		dialer,
   178  	)
   179  	privval.SignerDialerEndpointTimeoutReadWrite(100 * time.Millisecond)(dialerEndpoint)
   180  
   181  	signerServer := privval.NewSignerServer(
   182  		dialerEndpoint,
   183  		config.ChainID(),
   184  		types.NewMockPV(),
   185  	)
   186  
   187  	go func() {
   188  		err := signerServer.Start()
   189  		if err != nil {
   190  			panic(err)
   191  		}
   192  	}()
   193  	defer signerServer.Stop()
   194  
   195  	n, err := DefaultNewNode(config, genesisFile, log.NewTestingLogger(t))
   196  	require.NoError(t, err)
   197  	assert.IsType(t, &privval.SignerClient{}, n.PrivValidator())
   198  }
   199  
   200  // address without a protocol must result in error
   201  func TestPrivValidatorListenAddrNoProtocol(t *testing.T) {
   202  	addrNoPrefix := testFreeAddr(t)
   203  
   204  	config, genesisFile := cfg.ResetTestRoot("node_priv_val_tcp_test")
   205  	defer os.RemoveAll(config.RootDir)
   206  	config.BaseConfig.PrivValidatorListenAddr = addrNoPrefix
   207  
   208  	_, err := DefaultNewNode(config, genesisFile, log.NewTestingLogger(t))
   209  	assert.Error(t, err)
   210  }
   211  
   212  func TestNodeSetPrivValIPC(t *testing.T) {
   213  	tmpfile := "/tmp/kms." + random.RandStr(6) + ".sock"
   214  	defer os.Remove(tmpfile) // clean up
   215  
   216  	config, genesisFile := cfg.ResetTestRoot("node_priv_val_tcp_test")
   217  	defer os.RemoveAll(config.RootDir)
   218  	config.BaseConfig.PrivValidatorListenAddr = "unix://" + tmpfile
   219  
   220  	dialer := privval.DialUnixFn(tmpfile)
   221  	dialerEndpoint := privval.NewSignerDialerEndpoint(
   222  		log.NewTestingLogger(t),
   223  		dialer,
   224  	)
   225  	privval.SignerDialerEndpointTimeoutReadWrite(100 * time.Millisecond)(dialerEndpoint)
   226  
   227  	pvsc := privval.NewSignerServer(
   228  		dialerEndpoint,
   229  		config.ChainID(),
   230  		types.NewMockPV(),
   231  	)
   232  
   233  	go func() {
   234  		err := pvsc.Start()
   235  		require.NoError(t, err)
   236  	}()
   237  	defer pvsc.Stop()
   238  
   239  	n, err := DefaultNewNode(config, genesisFile, log.NewTestingLogger(t))
   240  	require.NoError(t, err)
   241  	assert.IsType(t, &privval.SignerClient{}, n.PrivValidator())
   242  }
   243  
   244  // testFreeAddr claims a free port so we don't block on listener being ready.
   245  func testFreeAddr(t *testing.T) string {
   246  	t.Helper()
   247  
   248  	ln, err := net.Listen("tcp", "127.0.0.1:0")
   249  	require.NoError(t, err)
   250  	defer ln.Close()
   251  
   252  	return fmt.Sprintf("127.0.0.1:%d", ln.Addr().(*net.TCPAddr).Port)
   253  }
   254  
   255  // create a proposal block using real and full
   256  // mempool pool and validate it.
   257  func TestCreateProposalBlock(t *testing.T) {
   258  	config, _ := cfg.ResetTestRoot("node_create_proposal")
   259  	defer os.RemoveAll(config.RootDir)
   260  	cc := proxy.NewLocalClientCreator(kvstore.NewKVStoreApplication())
   261  	proxyApp := appconn.NewAppConns(cc)
   262  	err := proxyApp.Start()
   263  	require.Nil(t, err)
   264  	defer proxyApp.Stop()
   265  
   266  	logger := log.NewTestingLogger(t)
   267  
   268  	var height int64 = 1
   269  	state, stateDB := state(1, height)
   270  	maxBlockBytes := 16384
   271  	state.ConsensusParams.Block.MaxBlockBytes = int64(maxBlockBytes)
   272  	proposerAddr, _ := state.Validators.GetByIndex(0)
   273  
   274  	// Make Mempool
   275  	mempool := mempl.NewCListMempool(
   276  		config.Mempool,
   277  		proxyApp.Mempool(),
   278  		state.LastBlockHeight,
   279  		state.ConsensusParams.Block.MaxTxBytes,
   280  		mempl.WithPreCheck(sm.TxPreCheck(state)),
   281  	)
   282  	mempool.SetLogger(logger)
   283  
   284  	// fill the mempool with more txs
   285  	// than can fit in a block
   286  	txLength := 1000
   287  	for i := 0; i < maxBlockBytes/txLength; i++ {
   288  		tx := random.RandBytes(txLength)
   289  		err := mempool.CheckTx(tx, nil)
   290  		assert.NoError(t, err)
   291  	}
   292  
   293  	blockExec := sm.NewBlockExecutor(
   294  		stateDB,
   295  		logger,
   296  		proxyApp.Consensus(),
   297  		mempool,
   298  	)
   299  
   300  	commit := types.NewCommit(types.BlockID{}, nil)
   301  	block, _ := blockExec.CreateProposalBlock(
   302  		height,
   303  		state, commit,
   304  		proposerAddr,
   305  	)
   306  
   307  	err = blockExec.ValidateBlock(state, block)
   308  	assert.NoError(t, err)
   309  }
   310  
   311  func TestNodeNewNodeCustomReactors(t *testing.T) {
   312  	config, genesisFile := cfg.ResetTestRoot("node_new_node_custom_reactors_test")
   313  	defer os.RemoveAll(config.RootDir)
   314  
   315  	cr := p2pmock.NewReactor()
   316  	customBlockchainReactor := p2pmock.NewReactor()
   317  
   318  	nodeKey, err := p2p.LoadOrGenNodeKey(config.NodeKeyFile())
   319  	require.NoError(t, err)
   320  
   321  	n, err := NewNode(config,
   322  		privval.LoadOrGenFilePV(config.PrivValidatorKeyFile(), config.PrivValidatorStateFile()),
   323  		nodeKey,
   324  		proxy.DefaultClientCreator(nil, config.ProxyApp, config.ABCI, config.DBDir()),
   325  		DefaultGenesisDocProviderFunc(genesisFile),
   326  		DefaultDBProvider,
   327  		log.NewTestingLogger(t),
   328  		CustomReactors(map[string]p2p.Reactor{"FOO": cr, "BLOCKCHAIN": customBlockchainReactor}),
   329  	)
   330  	require.NoError(t, err)
   331  
   332  	err = n.Start()
   333  	require.NoError(t, err)
   334  	defer n.Stop()
   335  
   336  	assert.True(t, cr.IsRunning())
   337  	assert.Equal(t, cr, n.Switch().Reactor("FOO"))
   338  
   339  	assert.True(t, customBlockchainReactor.IsRunning())
   340  	assert.Equal(t, customBlockchainReactor, n.Switch().Reactor("BLOCKCHAIN"))
   341  }
   342  
   343  func state(nVals int, height int64) (sm.State, dbm.DB) {
   344  	vals := make([]types.GenesisValidator, nVals)
   345  	for i := 0; i < nVals; i++ {
   346  		secret := []byte(fmt.Sprintf("test%d", i))
   347  		pk := ed25519.GenPrivKeyFromSecret(secret)
   348  		vals[i] = types.GenesisValidator{
   349  			Address: pk.PubKey().Address(),
   350  			PubKey:  pk.PubKey(),
   351  			Power:   1000,
   352  			Name:    fmt.Sprintf("test%d", i),
   353  		}
   354  	}
   355  	s, _ := sm.MakeGenesisState(&types.GenesisDoc{
   356  		ChainID:    "test-chain",
   357  		Validators: vals,
   358  		AppHash:    nil,
   359  	})
   360  
   361  	// save validators to db for 2 heights
   362  	stateDB := memdb.NewMemDB()
   363  	sm.SaveState(stateDB, s)
   364  
   365  	for i := 1; i < int(height); i++ {
   366  		s.LastBlockHeight++
   367  		s.LastValidators = s.Validators.Copy()
   368  		sm.SaveState(stateDB, s)
   369  	}
   370  	return s, stateDB
   371  }