github.com/badrootd/nibiru-cometbft@v0.37.5-0.20240307173500-2a75559eee9b/node/node_test.go (about) 1 package node 2 3 import ( 4 "context" 5 "fmt" 6 "net" 7 "os" 8 "syscall" 9 "testing" 10 "time" 11 12 "github.com/stretchr/testify/assert" 13 "github.com/stretchr/testify/require" 14 15 dbm "github.com/badrootd/nibiru-db" 16 17 "github.com/badrootd/nibiru-cometbft/abci/example/kvstore" 18 cfg "github.com/badrootd/nibiru-cometbft/config" 19 "github.com/badrootd/nibiru-cometbft/crypto/ed25519" 20 "github.com/badrootd/nibiru-cometbft/evidence" 21 "github.com/badrootd/nibiru-cometbft/libs/log" 22 cmtrand "github.com/badrootd/nibiru-cometbft/libs/rand" 23 mempl "github.com/badrootd/nibiru-cometbft/mempool" 24 mempoolv0 "github.com/badrootd/nibiru-cometbft/mempool/v0" 25 mempoolv1 "github.com/badrootd/nibiru-cometbft/mempool/v1" //nolint:staticcheck // SA1019 Priority mempool deprecated but still supported in this release. 26 "github.com/badrootd/nibiru-cometbft/p2p" 27 "github.com/badrootd/nibiru-cometbft/p2p/conn" 28 p2pmock "github.com/badrootd/nibiru-cometbft/p2p/mock" 29 "github.com/badrootd/nibiru-cometbft/privval" 30 "github.com/badrootd/nibiru-cometbft/proxy" 31 sm "github.com/badrootd/nibiru-cometbft/state" 32 "github.com/badrootd/nibiru-cometbft/store" 33 "github.com/badrootd/nibiru-cometbft/types" 34 cmttime "github.com/badrootd/nibiru-cometbft/types/time" 35 ) 36 37 func TestNodeStartStop(t *testing.T) { 38 config := cfg.ResetTestRoot("node_node_test") 39 defer os.RemoveAll(config.RootDir) 40 41 // create & start node 42 n, err := DefaultNewNode(config, log.TestingLogger()) 43 require.NoError(t, err) 44 err = n.Start() 45 require.NoError(t, err) 46 47 t.Logf("Started node %v", n.sw.NodeInfo()) 48 49 // wait for the node to produce a block 50 blocksSub, err := n.EventBus().Subscribe(context.Background(), "node_test", types.EventQueryNewBlock) 51 require.NoError(t, err) 52 select { 53 case <-blocksSub.Out(): 54 case <-blocksSub.Cancelled(): 55 t.Fatal("blocksSub was canceled") 56 case <-time.After(10 * time.Second): 57 t.Fatal("timed out waiting for the node to produce a block") 58 } 59 60 // stop the node 61 go func() { 62 err = n.Stop() 63 require.NoError(t, err) 64 }() 65 66 select { 67 case <-n.Quit(): 68 case <-time.After(5 * time.Second): 69 pid := os.Getpid() 70 p, err := os.FindProcess(pid) 71 if err != nil { 72 panic(err) 73 } 74 err = p.Signal(syscall.SIGABRT) 75 fmt.Println(err) 76 t.Fatal("timed out waiting for shutdown") 77 } 78 } 79 80 func TestSplitAndTrimEmpty(t *testing.T) { 81 testCases := []struct { 82 s string 83 sep string 84 cutset string 85 expected []string 86 }{ 87 {"a,b,c", ",", " ", []string{"a", "b", "c"}}, 88 {" a , b , c ", ",", " ", []string{"a", "b", "c"}}, 89 {" a, b, c ", ",", " ", []string{"a", "b", "c"}}, 90 {" a, ", ",", " ", []string{"a"}}, 91 {" ", ",", " ", []string{}}, 92 } 93 94 for _, tc := range testCases { 95 assert.Equal(t, tc.expected, splitAndTrimEmpty(tc.s, tc.sep, tc.cutset), "%s", tc.s) 96 } 97 } 98 99 func TestNodeDelayedStart(t *testing.T) { 100 config := cfg.ResetTestRoot("node_delayed_start_test") 101 defer os.RemoveAll(config.RootDir) 102 now := cmttime.Now() 103 104 // create & start node 105 n, err := DefaultNewNode(config, log.TestingLogger()) 106 n.GenesisDoc().GenesisTime = now.Add(2 * time.Second) 107 require.NoError(t, err) 108 109 err = n.Start() 110 require.NoError(t, err) 111 defer n.Stop() //nolint:errcheck // ignore for tests 112 113 startTime := cmttime.Now() 114 assert.Equal(t, true, startTime.After(n.GenesisDoc().GenesisTime)) 115 } 116 117 func TestNodeSetAppVersion(t *testing.T) { 118 config := cfg.ResetTestRoot("node_app_version_test") 119 defer os.RemoveAll(config.RootDir) 120 121 // create & start node 122 n, err := DefaultNewNode(config, log.TestingLogger()) 123 require.NoError(t, err) 124 125 // default config uses the kvstore app 126 appVersion := kvstore.ProtocolVersion 127 128 // check version is set in state 129 state, err := n.stateStore.Load() 130 require.NoError(t, err) 131 assert.Equal(t, state.Version.Consensus.App, appVersion) 132 133 // check version is set in node info 134 assert.Equal(t, n.nodeInfo.(p2p.DefaultNodeInfo).ProtocolVersion.App, appVersion) 135 } 136 137 func TestNodeSetPrivValTCP(t *testing.T) { 138 addr := "tcp://" + testFreeAddr(t) 139 140 config := cfg.ResetTestRoot("node_priv_val_tcp_test") 141 defer os.RemoveAll(config.RootDir) 142 config.BaseConfig.PrivValidatorListenAddr = addr 143 144 dialer := privval.DialTCPFn(addr, 100*time.Millisecond, ed25519.GenPrivKey()) 145 dialerEndpoint := privval.NewSignerDialerEndpoint( 146 log.TestingLogger(), 147 dialer, 148 ) 149 privval.SignerDialerEndpointTimeoutReadWrite(100 * time.Millisecond)(dialerEndpoint) 150 151 signerServer := privval.NewSignerServer( 152 dialerEndpoint, 153 config.ChainID(), 154 types.NewMockPV(), 155 ) 156 157 go func() { 158 err := signerServer.Start() 159 if err != nil { 160 panic(err) 161 } 162 }() 163 defer signerServer.Stop() //nolint:errcheck // ignore for tests 164 165 n, err := DefaultNewNode(config, log.TestingLogger()) 166 require.NoError(t, err) 167 assert.IsType(t, &privval.RetrySignerClient{}, n.PrivValidator()) 168 } 169 170 // address without a protocol must result in error 171 func TestPrivValidatorListenAddrNoProtocol(t *testing.T) { 172 addrNoPrefix := testFreeAddr(t) 173 174 config := cfg.ResetTestRoot("node_priv_val_tcp_test") 175 defer os.RemoveAll(config.RootDir) 176 config.BaseConfig.PrivValidatorListenAddr = addrNoPrefix 177 178 _, err := DefaultNewNode(config, log.TestingLogger()) 179 assert.Error(t, err) 180 } 181 182 func TestNodeSetPrivValIPC(t *testing.T) { 183 tmpfile := "/tmp/kms." + cmtrand.Str(6) + ".sock" 184 defer os.Remove(tmpfile) // clean up 185 186 config := cfg.ResetTestRoot("node_priv_val_tcp_test") 187 defer os.RemoveAll(config.RootDir) 188 config.BaseConfig.PrivValidatorListenAddr = "unix://" + tmpfile 189 190 dialer := privval.DialUnixFn(tmpfile) 191 dialerEndpoint := privval.NewSignerDialerEndpoint( 192 log.TestingLogger(), 193 dialer, 194 ) 195 privval.SignerDialerEndpointTimeoutReadWrite(100 * time.Millisecond)(dialerEndpoint) 196 197 pvsc := privval.NewSignerServer( 198 dialerEndpoint, 199 config.ChainID(), 200 types.NewMockPV(), 201 ) 202 203 go func() { 204 err := pvsc.Start() 205 require.NoError(t, err) 206 }() 207 defer pvsc.Stop() //nolint:errcheck // ignore for tests 208 209 n, err := DefaultNewNode(config, log.TestingLogger()) 210 require.NoError(t, err) 211 assert.IsType(t, &privval.RetrySignerClient{}, n.PrivValidator()) 212 } 213 214 // testFreeAddr claims a free port so we don't block on listener being ready. 215 func testFreeAddr(t *testing.T) string { 216 ln, err := net.Listen("tcp", "127.0.0.1:0") 217 require.NoError(t, err) 218 defer ln.Close() 219 220 return fmt.Sprintf("127.0.0.1:%d", ln.Addr().(*net.TCPAddr).Port) 221 } 222 223 // create a proposal block using real and full 224 // mempool and evidence pool and validate it. 225 func TestCreateProposalBlock(t *testing.T) { 226 config := cfg.ResetTestRoot("node_create_proposal") 227 defer os.RemoveAll(config.RootDir) 228 cc := proxy.NewLocalClientCreator(kvstore.NewApplication()) 229 proxyApp := proxy.NewAppConns(cc, proxy.NopMetrics()) 230 err := proxyApp.Start() 231 require.Nil(t, err) 232 defer proxyApp.Stop() //nolint:errcheck // ignore for tests 233 234 logger := log.TestingLogger() 235 236 var height int64 = 1 237 state, stateDB, privVals := state(1, height) 238 stateStore := sm.NewStore(stateDB, sm.StoreOptions{ 239 DiscardABCIResponses: false, 240 }) 241 maxBytes := 16384 242 var partSize uint32 = 256 243 maxEvidenceBytes := int64(maxBytes / 2) 244 state.ConsensusParams.Block.MaxBytes = int64(maxBytes) 245 state.ConsensusParams.Evidence.MaxBytes = maxEvidenceBytes 246 proposerAddr, _ := state.Validators.GetByIndex(0) 247 248 // Make Mempool 249 memplMetrics := mempl.NopMetrics() 250 var mempool mempl.Mempool 251 252 switch config.Mempool.Version { 253 case cfg.MempoolV0: 254 mempool = mempoolv0.NewCListMempool(config.Mempool, 255 proxyApp.Mempool(), 256 state.LastBlockHeight, 257 mempoolv0.WithMetrics(memplMetrics), 258 mempoolv0.WithPreCheck(sm.TxPreCheck(state)), 259 mempoolv0.WithPostCheck(sm.TxPostCheck(state))) 260 case cfg.MempoolV1: 261 mempool = mempoolv1.NewTxMempool(logger, 262 config.Mempool, 263 proxyApp.Mempool(), 264 state.LastBlockHeight, 265 mempoolv1.WithMetrics(memplMetrics), 266 mempoolv1.WithPreCheck(sm.TxPreCheck(state)), 267 mempoolv1.WithPostCheck(sm.TxPostCheck(state)), 268 ) 269 } 270 271 // Make EvidencePool 272 evidenceDB := dbm.NewMemDB() 273 blockStore := store.NewBlockStore(dbm.NewMemDB()) 274 evidencePool, err := evidence.NewPool(evidenceDB, stateStore, blockStore) 275 require.NoError(t, err) 276 evidencePool.SetLogger(logger) 277 278 // fill the evidence pool with more evidence 279 // than can fit in a block 280 var currentBytes int64 281 for currentBytes <= maxEvidenceBytes { 282 ev, err := types.NewMockDuplicateVoteEvidenceWithValidator(height, time.Now(), privVals[0], "test-chain") 283 require.NoError(t, err) 284 currentBytes += int64(len(ev.Bytes())) 285 evidencePool.ReportConflictingVotes(ev.VoteA, ev.VoteB) 286 } 287 288 evList, size := evidencePool.PendingEvidence(state.ConsensusParams.Evidence.MaxBytes) 289 require.Less(t, size, state.ConsensusParams.Evidence.MaxBytes+1) 290 evData := &types.EvidenceData{Evidence: evList} 291 require.EqualValues(t, size, evData.ByteSize()) 292 293 // fill the mempool with more txs 294 // than can fit in a block 295 txLength := 100 296 for i := 0; i <= maxBytes/txLength; i++ { 297 tx := cmtrand.Bytes(txLength) 298 err := mempool.CheckTx(tx, nil, mempl.TxInfo{}) 299 assert.NoError(t, err) 300 } 301 302 blockExec := sm.NewBlockExecutor( 303 stateStore, 304 logger, 305 proxyApp.Consensus(), 306 mempool, 307 evidencePool, 308 ) 309 310 commit := types.NewCommit(height-1, 0, types.BlockID{}, nil) 311 block, err := blockExec.CreateProposalBlock( 312 height, 313 state, commit, 314 proposerAddr, 315 ) 316 require.NoError(t, err) 317 318 // check that the part set does not exceed the maximum block size 319 partSet, err := block.MakePartSet(partSize) 320 require.NoError(t, err) 321 assert.Less(t, partSet.ByteSize(), int64(maxBytes)) 322 323 partSetFromHeader := types.NewPartSetFromHeader(partSet.Header()) 324 for partSetFromHeader.Count() < partSetFromHeader.Total() { 325 added, err := partSetFromHeader.AddPart(partSet.GetPart(int(partSetFromHeader.Count()))) 326 require.NoError(t, err) 327 require.True(t, added) 328 } 329 assert.EqualValues(t, partSetFromHeader.ByteSize(), partSet.ByteSize()) 330 331 err = blockExec.ValidateBlock(state, block) 332 assert.NoError(t, err) 333 } 334 335 func TestMaxProposalBlockSize(t *testing.T) { 336 config := cfg.ResetTestRoot("node_create_proposal") 337 defer os.RemoveAll(config.RootDir) 338 cc := proxy.NewLocalClientCreator(kvstore.NewApplication()) 339 proxyApp := proxy.NewAppConns(cc, proxy.NopMetrics()) 340 err := proxyApp.Start() 341 require.Nil(t, err) 342 defer proxyApp.Stop() //nolint:errcheck // ignore for tests 343 344 logger := log.TestingLogger() 345 346 var height int64 = 1 347 state, stateDB, _ := state(1, height) 348 stateStore := sm.NewStore(stateDB, sm.StoreOptions{ 349 DiscardABCIResponses: false, 350 }) 351 var maxBytes int64 = 16384 352 var partSize uint32 = 256 353 state.ConsensusParams.Block.MaxBytes = maxBytes 354 proposerAddr, _ := state.Validators.GetByIndex(0) 355 356 // Make Mempool 357 memplMetrics := mempl.NopMetrics() 358 var mempool mempl.Mempool 359 switch config.Mempool.Version { 360 case cfg.MempoolV0: 361 mempool = mempoolv0.NewCListMempool(config.Mempool, 362 proxyApp.Mempool(), 363 state.LastBlockHeight, 364 mempoolv0.WithMetrics(memplMetrics), 365 mempoolv0.WithPreCheck(sm.TxPreCheck(state)), 366 mempoolv0.WithPostCheck(sm.TxPostCheck(state))) 367 case cfg.MempoolV1: 368 mempool = mempoolv1.NewTxMempool(logger, 369 config.Mempool, 370 proxyApp.Mempool(), 371 state.LastBlockHeight, 372 mempoolv1.WithMetrics(memplMetrics), 373 mempoolv1.WithPreCheck(sm.TxPreCheck(state)), 374 mempoolv1.WithPostCheck(sm.TxPostCheck(state)), 375 ) 376 } 377 378 // fill the mempool with one txs just below the maximum size 379 txLength := int(types.MaxDataBytesNoEvidence(maxBytes, 1)) 380 tx := cmtrand.Bytes(txLength - 4) // to account for the varint 381 err = mempool.CheckTx(tx, nil, mempl.TxInfo{}) 382 assert.NoError(t, err) 383 384 blockExec := sm.NewBlockExecutor( 385 stateStore, 386 logger, 387 proxyApp.Consensus(), 388 mempool, 389 sm.EmptyEvidencePool{}, 390 ) 391 392 commit := types.NewCommit(height-1, 0, types.BlockID{}, nil) 393 block, err := blockExec.CreateProposalBlock( 394 height, 395 state, commit, 396 proposerAddr, 397 ) 398 require.NoError(t, err) 399 400 pb, err := block.ToProto() 401 require.NoError(t, err) 402 assert.Less(t, int64(pb.Size()), maxBytes) 403 404 // check that the part set does not exceed the maximum block size 405 partSet, err := block.MakePartSet(partSize) 406 require.NoError(t, err) 407 assert.EqualValues(t, partSet.ByteSize(), int64(pb.Size())) 408 } 409 410 func TestNodeNewNodeCustomReactors(t *testing.T) { 411 config := cfg.ResetTestRoot("node_new_node_custom_reactors_test") 412 defer os.RemoveAll(config.RootDir) 413 414 cr := p2pmock.NewReactor() 415 cr.Channels = []*conn.ChannelDescriptor{ 416 { 417 ID: byte(0x31), 418 Priority: 5, 419 SendQueueCapacity: 100, 420 RecvMessageCapacity: 100, 421 }, 422 } 423 customBlockchainReactor := p2pmock.NewReactor() 424 425 nodeKey, err := p2p.LoadOrGenNodeKey(config.NodeKeyFile()) 426 require.NoError(t, err) 427 428 n, err := NewNode(config, 429 privval.LoadOrGenFilePV(config.PrivValidatorKeyFile(), config.PrivValidatorStateFile()), 430 nodeKey, 431 proxy.DefaultClientCreator(config.ProxyApp, config.ABCI, config.DBDir()), 432 DefaultGenesisDocProviderFunc(config), 433 DefaultDBProvider, 434 DefaultMetricsProvider(config.Instrumentation), 435 log.TestingLogger(), 436 CustomReactors(map[string]p2p.Reactor{"FOO": cr, "BLOCKCHAIN": customBlockchainReactor}), 437 ) 438 require.NoError(t, err) 439 440 err = n.Start() 441 require.NoError(t, err) 442 defer n.Stop() //nolint:errcheck // ignore for tests 443 444 assert.True(t, cr.IsRunning()) 445 assert.Equal(t, cr, n.Switch().Reactor("FOO")) 446 447 assert.True(t, customBlockchainReactor.IsRunning()) 448 assert.Equal(t, customBlockchainReactor, n.Switch().Reactor("BLOCKCHAIN")) 449 450 channels := n.NodeInfo().(p2p.DefaultNodeInfo).Channels 451 assert.Contains(t, channels, mempl.MempoolChannel) 452 assert.Contains(t, channels, cr.Channels[0].ID) 453 } 454 455 func state(nVals int, height int64) (sm.State, dbm.DB, []types.PrivValidator) { 456 privVals := make([]types.PrivValidator, nVals) 457 vals := make([]types.GenesisValidator, nVals) 458 for i := 0; i < nVals; i++ { 459 privVal := types.NewMockPV() 460 privVals[i] = privVal 461 vals[i] = types.GenesisValidator{ 462 Address: privVal.PrivKey.PubKey().Address(), 463 PubKey: privVal.PrivKey.PubKey(), 464 Power: 1000, 465 Name: fmt.Sprintf("test%d", i), 466 } 467 } 468 s, _ := sm.MakeGenesisState(&types.GenesisDoc{ 469 ChainID: "test-chain", 470 Validators: vals, 471 AppHash: nil, 472 }) 473 474 // save validators to db for 2 heights 475 stateDB := dbm.NewMemDB() 476 stateStore := sm.NewStore(stateDB, sm.StoreOptions{ 477 DiscardABCIResponses: false, 478 }) 479 if err := stateStore.Save(s); err != nil { 480 panic(err) 481 } 482 483 for i := 1; i < int(height); i++ { 484 s.LastBlockHeight++ 485 s.LastValidators = s.Validators.Copy() 486 if err := stateStore.Save(s); err != nil { 487 panic(err) 488 } 489 } 490 return s, stateDB, privVals 491 }