github.com/evdatsion/aphelion-dpos-bft@v0.32.1/consensus/replay_test.go (about) 1 package consensus 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "io" 8 "io/ioutil" 9 "os" 10 "path/filepath" 11 "runtime" 12 "testing" 13 "time" 14 15 "github.com/stretchr/testify/assert" 16 "github.com/stretchr/testify/require" 17 18 "sort" 19 20 "github.com/evdatsion/aphelion-dpos-bft/abci/example/kvstore" 21 abci "github.com/evdatsion/aphelion-dpos-bft/abci/types" 22 cfg "github.com/evdatsion/aphelion-dpos-bft/config" 23 "github.com/evdatsion/aphelion-dpos-bft/crypto" 24 cmn "github.com/evdatsion/aphelion-dpos-bft/libs/common" 25 dbm "github.com/evdatsion/aphelion-dpos-bft/libs/db" 26 "github.com/evdatsion/aphelion-dpos-bft/libs/log" 27 "github.com/evdatsion/aphelion-dpos-bft/mock" 28 "github.com/evdatsion/aphelion-dpos-bft/privval" 29 "github.com/evdatsion/aphelion-dpos-bft/proxy" 30 sm "github.com/evdatsion/aphelion-dpos-bft/state" 31 "github.com/evdatsion/aphelion-dpos-bft/types" 32 tmtime "github.com/evdatsion/aphelion-dpos-bft/types/time" 33 "github.com/evdatsion/aphelion-dpos-bft/version" 34 ) 35 36 func TestMain(m *testing.M) { 37 config = ResetConfig("consensus_reactor_test") 38 consensusReplayConfig = ResetConfig("consensus_replay_test") 39 configStateTest := ResetConfig("consensus_state_test") 40 configMempoolTest := ResetConfig("consensus_mempool_test") 41 configByzantineTest := ResetConfig("consensus_byzantine_test") 42 code := m.Run() 43 os.RemoveAll(config.RootDir) 44 os.RemoveAll(consensusReplayConfig.RootDir) 45 os.RemoveAll(configStateTest.RootDir) 46 os.RemoveAll(configMempoolTest.RootDir) 47 os.RemoveAll(configByzantineTest.RootDir) 48 os.Exit(code) 49 } 50 51 // These tests ensure we can always recover from failure at any part of the consensus process. 52 // There are two general failure scenarios: failure during consensus, and failure while applying the block. 53 // Only the latter interacts with the app and store, 54 // but the former has to deal with restrictions on re-use of priv_validator keys. 55 // The `WAL Tests` are for failures during the consensus; 56 // the `Handshake Tests` are for failures in applying the block. 57 // With the help of the WAL, we can recover from it all! 58 59 //------------------------------------------------------------------------------------------ 60 // WAL Tests 61 62 // TODO: It would be better to verify explicitly which states we can recover from without the wal 63 // and which ones we need the wal for - then we'd also be able to only flush the 64 // wal writer when we need to, instead of with every message. 65 66 func startNewConsensusStateAndWaitForBlock(t *testing.T, consensusReplayConfig *cfg.Config, 67 lastBlockHeight int64, blockDB dbm.DB, stateDB dbm.DB) { 68 logger := log.TestingLogger() 69 state, _ := sm.LoadStateFromDBOrGenesisFile(stateDB, consensusReplayConfig.GenesisFile()) 70 privValidator := loadPrivValidator(consensusReplayConfig) 71 cs := newConsensusStateWithConfigAndBlockStore(consensusReplayConfig, state, privValidator, kvstore.NewKVStoreApplication(), blockDB) 72 cs.SetLogger(logger) 73 74 bytes, _ := ioutil.ReadFile(cs.config.WalFile()) 75 t.Logf("====== WAL: \n\r%X\n", bytes) 76 77 err := cs.Start() 78 require.NoError(t, err) 79 defer cs.Stop() 80 81 // This is just a signal that we haven't halted; its not something contained 82 // in the WAL itself. Assuming the consensus state is running, replay of any 83 // WAL, including the empty one, should eventually be followed by a new 84 // block, or else something is wrong. 85 newBlockSub, err := cs.eventBus.Subscribe(context.Background(), testSubscriber, types.EventQueryNewBlock) 86 require.NoError(t, err) 87 select { 88 case <-newBlockSub.Out(): 89 case <-newBlockSub.Cancelled(): 90 t.Fatal("newBlockSub was cancelled") 91 case <-time.After(120 * time.Second): 92 t.Fatal("Timed out waiting for new block (see trace above)") 93 } 94 } 95 96 func sendTxs(ctx context.Context, cs *ConsensusState) { 97 for i := 0; i < 256; i++ { 98 select { 99 case <-ctx.Done(): 100 return 101 default: 102 tx := []byte{byte(i)} 103 assertMempool(cs.txNotifier).CheckTx(tx, nil) 104 i++ 105 } 106 } 107 } 108 109 // TestWALCrash uses crashing WAL to test we can recover from any WAL failure. 110 func TestWALCrash(t *testing.T) { 111 testCases := []struct { 112 name string 113 initFn func(dbm.DB, *ConsensusState, context.Context) 114 heightToStop int64 115 }{ 116 {"empty block", 117 func(stateDB dbm.DB, cs *ConsensusState, ctx context.Context) {}, 118 1}, 119 {"many non-empty blocks", 120 func(stateDB dbm.DB, cs *ConsensusState, ctx context.Context) { 121 go sendTxs(ctx, cs) 122 }, 123 3}, 124 } 125 126 for i, tc := range testCases { 127 consensusReplayConfig := ResetConfig(fmt.Sprintf("%s_%d", t.Name(), i)) 128 t.Run(tc.name, func(t *testing.T) { 129 crashWALandCheckLiveness(t, consensusReplayConfig, tc.initFn, tc.heightToStop) 130 }) 131 } 132 } 133 134 func crashWALandCheckLiveness(t *testing.T, consensusReplayConfig *cfg.Config, 135 initFn func(dbm.DB, *ConsensusState, context.Context), heightToStop int64) { 136 walPanicked := make(chan error) 137 crashingWal := &crashingWAL{panicCh: walPanicked, heightToStop: heightToStop} 138 139 i := 1 140 LOOP: 141 for { 142 t.Logf("====== LOOP %d\n", i) 143 144 // create consensus state from a clean slate 145 logger := log.NewNopLogger() 146 blockDB := dbm.NewMemDB() 147 stateDB := blockDB 148 state, _ := sm.MakeGenesisStateFromFile(consensusReplayConfig.GenesisFile()) 149 privValidator := loadPrivValidator(consensusReplayConfig) 150 cs := newConsensusStateWithConfigAndBlockStore(consensusReplayConfig, state, privValidator, kvstore.NewKVStoreApplication(), blockDB) 151 cs.SetLogger(logger) 152 153 // start sending transactions 154 ctx, cancel := context.WithCancel(context.Background()) 155 initFn(stateDB, cs, ctx) 156 157 // clean up WAL file from the previous iteration 158 walFile := cs.config.WalFile() 159 os.Remove(walFile) 160 161 // set crashing WAL 162 csWal, err := cs.OpenWAL(walFile) 163 require.NoError(t, err) 164 crashingWal.next = csWal 165 // reset the message counter 166 crashingWal.msgIndex = 1 167 cs.wal = crashingWal 168 169 // start consensus state 170 err = cs.Start() 171 require.NoError(t, err) 172 173 i++ 174 175 select { 176 case err := <-walPanicked: 177 t.Logf("WAL panicked: %v", err) 178 179 // make sure we can make blocks after a crash 180 startNewConsensusStateAndWaitForBlock(t, consensusReplayConfig, cs.Height, blockDB, stateDB) 181 182 // stop consensus state and transactions sender (initFn) 183 cs.Stop() 184 cancel() 185 186 // if we reached the required height, exit 187 if _, ok := err.(ReachedHeightToStopError); ok { 188 break LOOP 189 } 190 case <-time.After(10 * time.Second): 191 t.Fatal("WAL did not panic for 10 seconds (check the log)") 192 } 193 } 194 } 195 196 // crashingWAL is a WAL which crashes or rather simulates a crash during Save 197 // (before and after). It remembers a message for which we last panicked 198 // (lastPanickedForMsgIndex), so we don't panic for it in subsequent iterations. 199 type crashingWAL struct { 200 next WAL 201 panicCh chan error 202 heightToStop int64 203 204 msgIndex int // current message index 205 lastPanickedForMsgIndex int // last message for which we panicked 206 } 207 208 var _ WAL = &crashingWAL{} 209 210 // WALWriteError indicates a WAL crash. 211 type WALWriteError struct { 212 msg string 213 } 214 215 func (e WALWriteError) Error() string { 216 return e.msg 217 } 218 219 // ReachedHeightToStopError indicates we've reached the required consensus 220 // height and may exit. 221 type ReachedHeightToStopError struct { 222 height int64 223 } 224 225 func (e ReachedHeightToStopError) Error() string { 226 return fmt.Sprintf("reached height to stop %d", e.height) 227 } 228 229 // Write simulate WAL's crashing by sending an error to the panicCh and then 230 // exiting the cs.receiveRoutine. 231 func (w *crashingWAL) Write(m WALMessage) { 232 if endMsg, ok := m.(EndHeightMessage); ok { 233 if endMsg.Height == w.heightToStop { 234 w.panicCh <- ReachedHeightToStopError{endMsg.Height} 235 runtime.Goexit() 236 } else { 237 w.next.Write(m) 238 } 239 return 240 } 241 242 if w.msgIndex > w.lastPanickedForMsgIndex { 243 w.lastPanickedForMsgIndex = w.msgIndex 244 _, file, line, _ := runtime.Caller(1) 245 w.panicCh <- WALWriteError{fmt.Sprintf("failed to write %T to WAL (fileline: %s:%d)", m, file, line)} 246 runtime.Goexit() 247 } else { 248 w.msgIndex++ 249 w.next.Write(m) 250 } 251 } 252 253 func (w *crashingWAL) WriteSync(m WALMessage) { 254 w.Write(m) 255 } 256 257 func (w *crashingWAL) FlushAndSync() error { return w.next.FlushAndSync() } 258 259 func (w *crashingWAL) SearchForEndHeight(height int64, options *WALSearchOptions) (rd io.ReadCloser, found bool, err error) { 260 return w.next.SearchForEndHeight(height, options) 261 } 262 263 func (w *crashingWAL) Start() error { return w.next.Start() } 264 func (w *crashingWAL) Stop() error { return w.next.Stop() } 265 func (w *crashingWAL) Wait() { w.next.Wait() } 266 267 //------------------------------------------------------------------------------------------ 268 type testSim struct { 269 GenesisState sm.State 270 Config *cfg.Config 271 Chain []*types.Block 272 Commits []*types.Commit 273 CleanupFunc cleanupFunc 274 } 275 276 const ( 277 numBlocks = 6 278 ) 279 280 var ( 281 mempool = mock.Mempool{} 282 evpool = sm.MockEvidencePool{} 283 284 sim testSim 285 ) 286 287 //--------------------------------------- 288 // Test handshake/replay 289 290 // 0 - all synced up 291 // 1 - saved block but app and state are behind 292 // 2 - save block and committed but state is behind 293 var modes = []uint{0, 1, 2} 294 295 // This is actually not a test, it's for storing validator change tx data for testHandshakeReplay 296 func TestSimulateValidatorsChange(t *testing.T) { 297 nPeers := 7 298 nVals := 4 299 css, genDoc, config, cleanup := randConsensusNetWithPeers(nVals, nPeers, "replay_test", newMockTickerFunc(true), newPersistentKVStoreWithPath) 300 sim.Config = config 301 sim.GenesisState, _ = sm.MakeGenesisState(genDoc) 302 sim.CleanupFunc = cleanup 303 304 partSize := types.BlockPartSizeBytes 305 306 newRoundCh := subscribe(css[0].eventBus, types.EventQueryNewRound) 307 proposalCh := subscribe(css[0].eventBus, types.EventQueryCompleteProposal) 308 309 vss := make([]*validatorStub, nPeers) 310 for i := 0; i < nPeers; i++ { 311 vss[i] = NewValidatorStub(css[i].privValidator, i) 312 } 313 height, round := css[0].Height, css[0].Round 314 // start the machine 315 startTestRound(css[0], height, round) 316 incrementHeight(vss...) 317 ensureNewRound(newRoundCh, height, 0) 318 ensureNewProposal(proposalCh, height, round) 319 rs := css[0].GetRoundState() 320 signAddVotes(css[0], types.PrecommitType, rs.ProposalBlock.Hash(), rs.ProposalBlockParts.Header(), vss[1:nVals]...) 321 ensureNewRound(newRoundCh, height+1, 0) 322 323 //height 2 324 height++ 325 incrementHeight(vss...) 326 newValidatorPubKey1 := css[nVals].privValidator.GetPubKey() 327 valPubKey1ABCI := types.TM2PB.PubKey(newValidatorPubKey1) 328 newValidatorTx1 := kvstore.MakeValSetChangeTx(valPubKey1ABCI, testMinPower) 329 err := assertMempool(css[0].txNotifier).CheckTx(newValidatorTx1, nil) 330 assert.Nil(t, err) 331 propBlock, _ := css[0].createProposalBlock() //changeProposer(t, cs1, vs2) 332 propBlockParts := propBlock.MakePartSet(partSize) 333 blockID := types.BlockID{Hash: propBlock.Hash(), PartsHeader: propBlockParts.Header()} 334 proposal := types.NewProposal(vss[1].Height, round, -1, blockID) 335 if err := vss[1].SignProposal(config.ChainID(), proposal); err != nil { 336 t.Fatal("failed to sign bad proposal", err) 337 } 338 339 // set the proposal block 340 if err := css[0].SetProposalAndBlock(proposal, propBlock, propBlockParts, "some peer"); err != nil { 341 t.Fatal(err) 342 } 343 ensureNewProposal(proposalCh, height, round) 344 rs = css[0].GetRoundState() 345 signAddVotes(css[0], types.PrecommitType, rs.ProposalBlock.Hash(), rs.ProposalBlockParts.Header(), vss[1:nVals]...) 346 ensureNewRound(newRoundCh, height+1, 0) 347 348 //height 3 349 height++ 350 incrementHeight(vss...) 351 updateValidatorPubKey1 := css[nVals].privValidator.GetPubKey() 352 updatePubKey1ABCI := types.TM2PB.PubKey(updateValidatorPubKey1) 353 updateValidatorTx1 := kvstore.MakeValSetChangeTx(updatePubKey1ABCI, 25) 354 err = assertMempool(css[0].txNotifier).CheckTx(updateValidatorTx1, nil) 355 assert.Nil(t, err) 356 propBlock, _ = css[0].createProposalBlock() //changeProposer(t, cs1, vs2) 357 propBlockParts = propBlock.MakePartSet(partSize) 358 blockID = types.BlockID{Hash: propBlock.Hash(), PartsHeader: propBlockParts.Header()} 359 proposal = types.NewProposal(vss[2].Height, round, -1, blockID) 360 if err := vss[2].SignProposal(config.ChainID(), proposal); err != nil { 361 t.Fatal("failed to sign bad proposal", err) 362 } 363 364 // set the proposal block 365 if err := css[0].SetProposalAndBlock(proposal, propBlock, propBlockParts, "some peer"); err != nil { 366 t.Fatal(err) 367 } 368 ensureNewProposal(proposalCh, height, round) 369 rs = css[0].GetRoundState() 370 signAddVotes(css[0], types.PrecommitType, rs.ProposalBlock.Hash(), rs.ProposalBlockParts.Header(), vss[1:nVals]...) 371 ensureNewRound(newRoundCh, height+1, 0) 372 373 //height 4 374 height++ 375 incrementHeight(vss...) 376 newValidatorPubKey2 := css[nVals+1].privValidator.GetPubKey() 377 newVal2ABCI := types.TM2PB.PubKey(newValidatorPubKey2) 378 newValidatorTx2 := kvstore.MakeValSetChangeTx(newVal2ABCI, testMinPower) 379 err = assertMempool(css[0].txNotifier).CheckTx(newValidatorTx2, nil) 380 assert.Nil(t, err) 381 newValidatorPubKey3 := css[nVals+2].privValidator.GetPubKey() 382 newVal3ABCI := types.TM2PB.PubKey(newValidatorPubKey3) 383 newValidatorTx3 := kvstore.MakeValSetChangeTx(newVal3ABCI, testMinPower) 384 err = assertMempool(css[0].txNotifier).CheckTx(newValidatorTx3, nil) 385 assert.Nil(t, err) 386 propBlock, _ = css[0].createProposalBlock() //changeProposer(t, cs1, vs2) 387 propBlockParts = propBlock.MakePartSet(partSize) 388 blockID = types.BlockID{Hash: propBlock.Hash(), PartsHeader: propBlockParts.Header()} 389 newVss := make([]*validatorStub, nVals+1) 390 copy(newVss, vss[:nVals+1]) 391 sort.Sort(ValidatorStubsByAddress(newVss)) 392 selfIndex := 0 393 for i, vs := range newVss { 394 if vs.GetPubKey().Equals(css[0].privValidator.GetPubKey()) { 395 selfIndex = i 396 break 397 } 398 } 399 400 proposal = types.NewProposal(vss[3].Height, round, -1, blockID) 401 if err := vss[3].SignProposal(config.ChainID(), proposal); err != nil { 402 t.Fatal("failed to sign bad proposal", err) 403 } 404 405 // set the proposal block 406 if err := css[0].SetProposalAndBlock(proposal, propBlock, propBlockParts, "some peer"); err != nil { 407 t.Fatal(err) 408 } 409 ensureNewProposal(proposalCh, height, round) 410 411 removeValidatorTx2 := kvstore.MakeValSetChangeTx(newVal2ABCI, 0) 412 err = assertMempool(css[0].txNotifier).CheckTx(removeValidatorTx2, nil) 413 assert.Nil(t, err) 414 415 rs = css[0].GetRoundState() 416 for i := 0; i < nVals+1; i++ { 417 if i == selfIndex { 418 continue 419 } 420 signAddVotes(css[0], types.PrecommitType, rs.ProposalBlock.Hash(), rs.ProposalBlockParts.Header(), newVss[i]) 421 } 422 423 ensureNewRound(newRoundCh, height+1, 0) 424 425 //height 5 426 height++ 427 incrementHeight(vss...) 428 ensureNewProposal(proposalCh, height, round) 429 rs = css[0].GetRoundState() 430 for i := 0; i < nVals+1; i++ { 431 if i == selfIndex { 432 continue 433 } 434 signAddVotes(css[0], types.PrecommitType, rs.ProposalBlock.Hash(), rs.ProposalBlockParts.Header(), newVss[i]) 435 } 436 ensureNewRound(newRoundCh, height+1, 0) 437 438 //height 6 439 height++ 440 incrementHeight(vss...) 441 removeValidatorTx3 := kvstore.MakeValSetChangeTx(newVal3ABCI, 0) 442 err = assertMempool(css[0].txNotifier).CheckTx(removeValidatorTx3, nil) 443 assert.Nil(t, err) 444 propBlock, _ = css[0].createProposalBlock() //changeProposer(t, cs1, vs2) 445 propBlockParts = propBlock.MakePartSet(partSize) 446 blockID = types.BlockID{Hash: propBlock.Hash(), PartsHeader: propBlockParts.Header()} 447 newVss = make([]*validatorStub, nVals+3) 448 copy(newVss, vss[:nVals+3]) 449 sort.Sort(ValidatorStubsByAddress(newVss)) 450 for i, vs := range newVss { 451 if vs.GetPubKey().Equals(css[0].privValidator.GetPubKey()) { 452 selfIndex = i 453 break 454 } 455 } 456 proposal = types.NewProposal(vss[1].Height, round, -1, blockID) 457 if err := vss[1].SignProposal(config.ChainID(), proposal); err != nil { 458 t.Fatal("failed to sign bad proposal", err) 459 } 460 461 // set the proposal block 462 if err := css[0].SetProposalAndBlock(proposal, propBlock, propBlockParts, "some peer"); err != nil { 463 t.Fatal(err) 464 } 465 ensureNewProposal(proposalCh, height, round) 466 rs = css[0].GetRoundState() 467 for i := 0; i < nVals+3; i++ { 468 if i == selfIndex { 469 continue 470 } 471 signAddVotes(css[0], types.PrecommitType, rs.ProposalBlock.Hash(), rs.ProposalBlockParts.Header(), newVss[i]) 472 } 473 ensureNewRound(newRoundCh, height+1, 0) 474 475 sim.Chain = make([]*types.Block, 0) 476 sim.Commits = make([]*types.Commit, 0) 477 for i := 1; i <= numBlocks; i++ { 478 sim.Chain = append(sim.Chain, css[0].blockStore.LoadBlock(int64(i))) 479 sim.Commits = append(sim.Commits, css[0].blockStore.LoadBlockCommit(int64(i))) 480 } 481 } 482 483 // Sync from scratch 484 func TestHandshakeReplayAll(t *testing.T) { 485 for _, m := range modes { 486 testHandshakeReplay(t, config, 0, m, false) 487 } 488 for _, m := range modes { 489 testHandshakeReplay(t, config, 0, m, true) 490 } 491 } 492 493 // Sync many, not from scratch 494 func TestHandshakeReplaySome(t *testing.T) { 495 for _, m := range modes { 496 testHandshakeReplay(t, config, 1, m, false) 497 } 498 for _, m := range modes { 499 testHandshakeReplay(t, config, 1, m, true) 500 } 501 } 502 503 // Sync from lagging by one 504 func TestHandshakeReplayOne(t *testing.T) { 505 for _, m := range modes { 506 testHandshakeReplay(t, config, numBlocks-1, m, false) 507 } 508 for _, m := range modes { 509 testHandshakeReplay(t, config, numBlocks-1, m, true) 510 } 511 } 512 513 // Sync from caught up 514 func TestHandshakeReplayNone(t *testing.T) { 515 for _, m := range modes { 516 testHandshakeReplay(t, config, numBlocks, m, false) 517 } 518 for _, m := range modes { 519 testHandshakeReplay(t, config, numBlocks, m, true) 520 } 521 } 522 523 // Test mockProxyApp should not panic when app return ABCIResponses with some empty ResponseDeliverTx 524 func TestMockProxyApp(t *testing.T) { 525 sim.CleanupFunc() //clean the test env created in TestSimulateValidatorsChange 526 logger := log.TestingLogger() 527 var validTxs, invalidTxs = 0, 0 528 txIndex := 0 529 530 assert.NotPanics(t, func() { 531 abciResWithEmptyDeliverTx := new(sm.ABCIResponses) 532 abciResWithEmptyDeliverTx.DeliverTx = make([]*abci.ResponseDeliverTx, 0) 533 abciResWithEmptyDeliverTx.DeliverTx = append(abciResWithEmptyDeliverTx.DeliverTx, &abci.ResponseDeliverTx{}) 534 535 // called when saveABCIResponses: 536 bytes := cdc.MustMarshalBinaryBare(abciResWithEmptyDeliverTx) 537 loadedAbciRes := new(sm.ABCIResponses) 538 539 // this also happens sm.LoadABCIResponses 540 err := cdc.UnmarshalBinaryBare(bytes, loadedAbciRes) 541 require.NoError(t, err) 542 543 mock := newMockProxyApp([]byte("mock_hash"), loadedAbciRes) 544 545 abciRes := new(sm.ABCIResponses) 546 abciRes.DeliverTx = make([]*abci.ResponseDeliverTx, len(loadedAbciRes.DeliverTx)) 547 // Execute transactions and get hash. 548 proxyCb := func(req *abci.Request, res *abci.Response) { 549 switch r := res.Value.(type) { 550 case *abci.Response_DeliverTx: 551 // TODO: make use of res.Log 552 // TODO: make use of this info 553 // Blocks may include invalid txs. 554 txRes := r.DeliverTx 555 if txRes.Code == abci.CodeTypeOK { 556 validTxs++ 557 } else { 558 logger.Debug("Invalid tx", "code", txRes.Code, "log", txRes.Log) 559 invalidTxs++ 560 } 561 abciRes.DeliverTx[txIndex] = txRes 562 txIndex++ 563 } 564 } 565 mock.SetResponseCallback(proxyCb) 566 567 someTx := []byte("tx") 568 mock.DeliverTxAsync(abci.RequestDeliverTx{Tx: someTx}) 569 }) 570 assert.True(t, validTxs == 1) 571 assert.True(t, invalidTxs == 0) 572 } 573 574 func tempWALWithData(data []byte) string { 575 walFile, err := ioutil.TempFile("", "wal") 576 if err != nil { 577 panic(fmt.Sprintf("failed to create temp WAL file: %v", err)) 578 } 579 _, err = walFile.Write(data) 580 if err != nil { 581 panic(fmt.Sprintf("failed to write to temp WAL file: %v", err)) 582 } 583 if err := walFile.Close(); err != nil { 584 panic(fmt.Sprintf("failed to close temp WAL file: %v", err)) 585 } 586 return walFile.Name() 587 } 588 589 // Make some blocks. Start a fresh app and apply nBlocks blocks. Then restart the app and sync it up with the remaining blocks 590 func testHandshakeReplay(t *testing.T, config *cfg.Config, nBlocks int, mode uint, testValidatorsChange bool) { 591 var chain []*types.Block 592 var commits []*types.Commit 593 var store *mockBlockStore 594 var stateDB dbm.DB 595 var genisisState sm.State 596 if testValidatorsChange { 597 testConfig := ResetConfig(fmt.Sprintf("%s_%v_m", t.Name(), mode)) 598 defer os.RemoveAll(testConfig.RootDir) 599 stateDB = dbm.NewMemDB() 600 genisisState = sim.GenesisState 601 config = sim.Config 602 chain = sim.Chain 603 commits = sim.Commits 604 store = newMockBlockStore(config, genisisState.ConsensusParams) 605 } else { //test single node 606 testConfig := ResetConfig(fmt.Sprintf("%s_%v_s", t.Name(), mode)) 607 defer os.RemoveAll(testConfig.RootDir) 608 walBody, err := WALWithNBlocks(t, numBlocks) 609 require.NoError(t, err) 610 walFile := tempWALWithData(walBody) 611 config.Consensus.SetWalFile(walFile) 612 613 privVal := privval.LoadFilePV(config.PrivValidatorKeyFile(), config.PrivValidatorStateFile()) 614 615 wal, err := NewWAL(walFile) 616 require.NoError(t, err) 617 wal.SetLogger(log.TestingLogger()) 618 err = wal.Start() 619 require.NoError(t, err) 620 defer wal.Stop() 621 622 chain, commits, err = makeBlockchainFromWAL(wal) 623 require.NoError(t, err) 624 stateDB, genisisState, store = stateAndStore(config, privVal.GetPubKey(), kvstore.ProtocolVersion) 625 } 626 store.chain = chain 627 store.commits = commits 628 629 state := genisisState.Copy() 630 // run the chain through state.ApplyBlock to build up the tendermint state 631 state = buildTMStateFromChain(config, stateDB, state, chain, nBlocks, mode) 632 latestAppHash := state.AppHash 633 634 // make a new client creator 635 kvstoreApp := kvstore.NewPersistentKVStoreApplication(filepath.Join(config.DBDir(), fmt.Sprintf("replay_test_%d_%d_a", nBlocks, mode))) 636 637 clientCreator2 := proxy.NewLocalClientCreator(kvstoreApp) 638 if nBlocks > 0 { 639 // run nBlocks against a new client to build up the app state. 640 // use a throwaway tendermint state 641 proxyApp := proxy.NewAppConns(clientCreator2) 642 stateDB1 := dbm.NewMemDB() 643 sm.SaveState(stateDB1, genisisState) 644 buildAppStateFromChain(proxyApp, stateDB1, genisisState, chain, nBlocks, mode) 645 } 646 647 // now start the app using the handshake - it should sync 648 genDoc, _ := sm.MakeGenesisDocFromFile(config.GenesisFile()) 649 handshaker := NewHandshaker(stateDB, state, store, genDoc) 650 proxyApp := proxy.NewAppConns(clientCreator2) 651 if err := proxyApp.Start(); err != nil { 652 t.Fatalf("Error starting proxy app connections: %v", err) 653 } 654 defer proxyApp.Stop() 655 if err := handshaker.Handshake(proxyApp); err != nil { 656 t.Fatalf("Error on abci handshake: %v", err) 657 } 658 659 // get the latest app hash from the app 660 res, err := proxyApp.Query().InfoSync(abci.RequestInfo{Version: ""}) 661 if err != nil { 662 t.Fatal(err) 663 } 664 665 // the app hash should be synced up 666 if !bytes.Equal(latestAppHash, res.LastBlockAppHash) { 667 t.Fatalf("Expected app hashes to match after handshake/replay. got %X, expected %X", res.LastBlockAppHash, latestAppHash) 668 } 669 670 expectedBlocksToSync := numBlocks - nBlocks 671 if nBlocks == numBlocks && mode > 0 { 672 expectedBlocksToSync++ 673 } else if nBlocks > 0 && mode == 1 { 674 expectedBlocksToSync++ 675 } 676 677 if handshaker.NBlocks() != expectedBlocksToSync { 678 t.Fatalf("Expected handshake to sync %d blocks, got %d", expectedBlocksToSync, handshaker.NBlocks()) 679 } 680 } 681 682 func applyBlock(stateDB dbm.DB, st sm.State, blk *types.Block, proxyApp proxy.AppConns) sm.State { 683 testPartSize := types.BlockPartSizeBytes 684 blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), proxyApp.Consensus(), mempool, evpool) 685 686 blkID := types.BlockID{Hash: blk.Hash(), PartsHeader: blk.MakePartSet(testPartSize).Header()} 687 newState, err := blockExec.ApplyBlock(st, blkID, blk) 688 if err != nil { 689 panic(err) 690 } 691 return newState 692 } 693 694 func buildAppStateFromChain(proxyApp proxy.AppConns, stateDB dbm.DB, 695 state sm.State, chain []*types.Block, nBlocks int, mode uint) { 696 // start a new app without handshake, play nBlocks blocks 697 if err := proxyApp.Start(); err != nil { 698 panic(err) 699 } 700 defer proxyApp.Stop() 701 702 state.Version.Consensus.App = kvstore.ProtocolVersion //simulate handshake, receive app version 703 validators := types.TM2PB.ValidatorUpdates(state.Validators) 704 if _, err := proxyApp.Consensus().InitChainSync(abci.RequestInitChain{ 705 Validators: validators, 706 }); err != nil { 707 panic(err) 708 } 709 sm.SaveState(stateDB, state) //save height 1's validatorsInfo 710 711 switch mode { 712 case 0: 713 for i := 0; i < nBlocks; i++ { 714 block := chain[i] 715 state = applyBlock(stateDB, state, block, proxyApp) 716 } 717 case 1, 2: 718 for i := 0; i < nBlocks-1; i++ { 719 block := chain[i] 720 state = applyBlock(stateDB, state, block, proxyApp) 721 } 722 723 if mode == 2 { 724 // update the kvstore height and apphash 725 // as if we ran commit but not 726 state = applyBlock(stateDB, state, chain[nBlocks-1], proxyApp) 727 } 728 } 729 730 } 731 732 func buildTMStateFromChain(config *cfg.Config, stateDB dbm.DB, state sm.State, chain []*types.Block, nBlocks int, mode uint) sm.State { 733 // run the whole chain against this client to build up the tendermint state 734 clientCreator := proxy.NewLocalClientCreator(kvstore.NewPersistentKVStoreApplication(filepath.Join(config.DBDir(), fmt.Sprintf("replay_test_%d_%d_t", nBlocks, mode)))) 735 proxyApp := proxy.NewAppConns(clientCreator) 736 if err := proxyApp.Start(); err != nil { 737 panic(err) 738 } 739 defer proxyApp.Stop() 740 741 state.Version.Consensus.App = kvstore.ProtocolVersion //simulate handshake, receive app version 742 validators := types.TM2PB.ValidatorUpdates(state.Validators) 743 if _, err := proxyApp.Consensus().InitChainSync(abci.RequestInitChain{ 744 Validators: validators, 745 }); err != nil { 746 panic(err) 747 } 748 sm.SaveState(stateDB, state) //save height 1's validatorsInfo 749 750 switch mode { 751 case 0: 752 // sync right up 753 for _, block := range chain { 754 state = applyBlock(stateDB, state, block, proxyApp) 755 } 756 757 case 1, 2: 758 // sync up to the penultimate as if we stored the block. 759 // whether we commit or not depends on the appHash 760 for _, block := range chain[:len(chain)-1] { 761 state = applyBlock(stateDB, state, block, proxyApp) 762 } 763 764 // apply the final block to a state copy so we can 765 // get the right next appHash but keep the state back 766 applyBlock(stateDB, state, chain[len(chain)-1], proxyApp) 767 } 768 769 return state 770 } 771 772 func TestHandshakePanicsIfAppReturnsWrongAppHash(t *testing.T) { 773 // 1. Initialize tendermint and commit 3 blocks with the following app hashes: 774 // - 0x01 775 // - 0x02 776 // - 0x03 777 config := ResetConfig("handshake_test_") 778 defer os.RemoveAll(config.RootDir) 779 privVal := privval.LoadFilePV(config.PrivValidatorKeyFile(), config.PrivValidatorStateFile()) 780 const appVersion = 0x0 781 stateDB, state, store := stateAndStore(config, privVal.GetPubKey(), appVersion) 782 genDoc, _ := sm.MakeGenesisDocFromFile(config.GenesisFile()) 783 state.LastValidators = state.Validators.Copy() 784 // mode = 0 for committing all the blocks 785 blocks := makeBlocks(3, &state, privVal) 786 store.chain = blocks 787 788 // 2. Tendermint must panic if app returns wrong hash for the first block 789 // - RANDOM HASH 790 // - 0x02 791 // - 0x03 792 { 793 app := &badApp{numBlocks: 3, allHashesAreWrong: true} 794 clientCreator := proxy.NewLocalClientCreator(app) 795 proxyApp := proxy.NewAppConns(clientCreator) 796 err := proxyApp.Start() 797 require.NoError(t, err) 798 defer proxyApp.Stop() 799 800 assert.Panics(t, func() { 801 h := NewHandshaker(stateDB, state, store, genDoc) 802 h.Handshake(proxyApp) 803 }) 804 } 805 806 // 3. Tendermint must panic if app returns wrong hash for the last block 807 // - 0x01 808 // - 0x02 809 // - RANDOM HASH 810 { 811 app := &badApp{numBlocks: 3, onlyLastHashIsWrong: true} 812 clientCreator := proxy.NewLocalClientCreator(app) 813 proxyApp := proxy.NewAppConns(clientCreator) 814 err := proxyApp.Start() 815 require.NoError(t, err) 816 defer proxyApp.Stop() 817 818 assert.Panics(t, func() { 819 h := NewHandshaker(stateDB, state, store, genDoc) 820 h.Handshake(proxyApp) 821 }) 822 } 823 } 824 825 func makeBlocks(n int, state *sm.State, privVal types.PrivValidator) []*types.Block { 826 blocks := make([]*types.Block, 0) 827 828 var ( 829 prevBlock *types.Block 830 prevBlockMeta *types.BlockMeta 831 ) 832 833 appHeight := byte(0x01) 834 for i := 0; i < n; i++ { 835 height := int64(i + 1) 836 837 block, parts := makeBlock(*state, prevBlock, prevBlockMeta, privVal, height) 838 blocks = append(blocks, block) 839 840 prevBlock = block 841 prevBlockMeta = types.NewBlockMeta(block, parts) 842 843 // update state 844 state.AppHash = []byte{appHeight} 845 appHeight++ 846 state.LastBlockHeight = height 847 } 848 849 return blocks 850 } 851 852 func makeVote(header *types.Header, blockID types.BlockID, valset *types.ValidatorSet, privVal types.PrivValidator) *types.Vote { 853 addr := privVal.GetPubKey().Address() 854 idx, _ := valset.GetByAddress(addr) 855 vote := &types.Vote{ 856 ValidatorAddress: addr, 857 ValidatorIndex: idx, 858 Height: header.Height, 859 Round: 1, 860 Timestamp: tmtime.Now(), 861 Type: types.PrecommitType, 862 BlockID: blockID, 863 } 864 865 privVal.SignVote(header.ChainID, vote) 866 867 return vote 868 } 869 870 func makeBlock(state sm.State, lastBlock *types.Block, lastBlockMeta *types.BlockMeta, 871 privVal types.PrivValidator, height int64) (*types.Block, *types.PartSet) { 872 873 lastCommit := types.NewCommit(types.BlockID{}, nil) 874 if height > 1 { 875 vote := makeVote(&lastBlock.Header, lastBlockMeta.BlockID, state.Validators, privVal).CommitSig() 876 lastCommit = types.NewCommit(lastBlockMeta.BlockID, []*types.CommitSig{vote}) 877 } 878 879 return state.MakeBlock(height, []types.Tx{}, lastCommit, nil, state.Validators.GetProposer().Address) 880 } 881 882 type badApp struct { 883 abci.BaseApplication 884 numBlocks byte 885 height byte 886 allHashesAreWrong bool 887 onlyLastHashIsWrong bool 888 } 889 890 func (app *badApp) Commit() abci.ResponseCommit { 891 app.height++ 892 if app.onlyLastHashIsWrong { 893 if app.height == app.numBlocks { 894 return abci.ResponseCommit{Data: cmn.RandBytes(8)} 895 } 896 return abci.ResponseCommit{Data: []byte{app.height}} 897 } else if app.allHashesAreWrong { 898 return abci.ResponseCommit{Data: cmn.RandBytes(8)} 899 } 900 901 panic("either allHashesAreWrong or onlyLastHashIsWrong must be set") 902 } 903 904 //-------------------------- 905 // utils for making blocks 906 907 func makeBlockchainFromWAL(wal WAL) ([]*types.Block, []*types.Commit, error) { 908 var height int64 909 910 // Search for height marker 911 gr, found, err := wal.SearchForEndHeight(height, &WALSearchOptions{}) 912 if err != nil { 913 return nil, nil, err 914 } 915 if !found { 916 return nil, nil, fmt.Errorf("WAL does not contain height %d", height) 917 } 918 defer gr.Close() // nolint: errcheck 919 920 // log.Notice("Build a blockchain by reading from the WAL") 921 922 var ( 923 blocks []*types.Block 924 commits []*types.Commit 925 thisBlockParts *types.PartSet 926 thisBlockCommit *types.Commit 927 ) 928 929 dec := NewWALDecoder(gr) 930 for { 931 msg, err := dec.Decode() 932 if err == io.EOF { 933 break 934 } else if err != nil { 935 return nil, nil, err 936 } 937 938 piece := readPieceFromWAL(msg) 939 if piece == nil { 940 continue 941 } 942 943 switch p := piece.(type) { 944 case EndHeightMessage: 945 // if its not the first one, we have a full block 946 if thisBlockParts != nil { 947 var block = new(types.Block) 948 _, err = cdc.UnmarshalBinaryLengthPrefixedReader(thisBlockParts.GetReader(), block, 0) 949 if err != nil { 950 panic(err) 951 } 952 if block.Height != height+1 { 953 panic(fmt.Sprintf("read bad block from wal. got height %d, expected %d", block.Height, height+1)) 954 } 955 commitHeight := thisBlockCommit.Precommits[0].Height 956 if commitHeight != height+1 { 957 panic(fmt.Sprintf("commit doesnt match. got height %d, expected %d", commitHeight, height+1)) 958 } 959 blocks = append(blocks, block) 960 commits = append(commits, thisBlockCommit) 961 height++ 962 } 963 case *types.PartSetHeader: 964 thisBlockParts = types.NewPartSetFromHeader(*p) 965 case *types.Part: 966 _, err := thisBlockParts.AddPart(p) 967 if err != nil { 968 return nil, nil, err 969 } 970 case *types.Vote: 971 if p.Type == types.PrecommitType { 972 commitSigs := []*types.CommitSig{p.CommitSig()} 973 thisBlockCommit = types.NewCommit(p.BlockID, commitSigs) 974 } 975 } 976 } 977 // grab the last block too 978 var block = new(types.Block) 979 _, err = cdc.UnmarshalBinaryLengthPrefixedReader(thisBlockParts.GetReader(), block, 0) 980 if err != nil { 981 panic(err) 982 } 983 if block.Height != height+1 { 984 panic(fmt.Sprintf("read bad block from wal. got height %d, expected %d", block.Height, height+1)) 985 } 986 commitHeight := thisBlockCommit.Precommits[0].Height 987 if commitHeight != height+1 { 988 panic(fmt.Sprintf("commit doesnt match. got height %d, expected %d", commitHeight, height+1)) 989 } 990 blocks = append(blocks, block) 991 commits = append(commits, thisBlockCommit) 992 return blocks, commits, nil 993 } 994 995 func readPieceFromWAL(msg *TimedWALMessage) interface{} { 996 // for logging 997 switch m := msg.Msg.(type) { 998 case msgInfo: 999 switch msg := m.Msg.(type) { 1000 case *ProposalMessage: 1001 return &msg.Proposal.BlockID.PartsHeader 1002 case *BlockPartMessage: 1003 return msg.Part 1004 case *VoteMessage: 1005 return msg.Vote 1006 } 1007 case EndHeightMessage: 1008 return m 1009 } 1010 1011 return nil 1012 } 1013 1014 // fresh state and mock store 1015 func stateAndStore(config *cfg.Config, pubKey crypto.PubKey, appVersion version.Protocol) (dbm.DB, sm.State, *mockBlockStore) { 1016 stateDB := dbm.NewMemDB() 1017 state, _ := sm.MakeGenesisStateFromFile(config.GenesisFile()) 1018 state.Version.Consensus.App = appVersion 1019 store := newMockBlockStore(config, state.ConsensusParams) 1020 sm.SaveState(stateDB, state) 1021 return stateDB, state, store 1022 } 1023 1024 //---------------------------------- 1025 // mock block store 1026 1027 type mockBlockStore struct { 1028 config *cfg.Config 1029 params types.ConsensusParams 1030 chain []*types.Block 1031 commits []*types.Commit 1032 } 1033 1034 // TODO: NewBlockStore(db.NewMemDB) ... 1035 func newMockBlockStore(config *cfg.Config, params types.ConsensusParams) *mockBlockStore { 1036 return &mockBlockStore{config, params, nil, nil} 1037 } 1038 1039 func (bs *mockBlockStore) Height() int64 { return int64(len(bs.chain)) } 1040 func (bs *mockBlockStore) LoadBlock(height int64) *types.Block { return bs.chain[height-1] } 1041 func (bs *mockBlockStore) LoadBlockMeta(height int64) *types.BlockMeta { 1042 block := bs.chain[height-1] 1043 return &types.BlockMeta{ 1044 BlockID: types.BlockID{Hash: block.Hash(), PartsHeader: block.MakePartSet(types.BlockPartSizeBytes).Header()}, 1045 Header: block.Header, 1046 } 1047 } 1048 func (bs *mockBlockStore) LoadBlockPart(height int64, index int) *types.Part { return nil } 1049 func (bs *mockBlockStore) SaveBlock(block *types.Block, blockParts *types.PartSet, seenCommit *types.Commit) { 1050 } 1051 func (bs *mockBlockStore) LoadBlockCommit(height int64) *types.Commit { 1052 return bs.commits[height-1] 1053 } 1054 func (bs *mockBlockStore) LoadSeenCommit(height int64) *types.Commit { 1055 return bs.commits[height-1] 1056 } 1057 1058 //--------------------------------------- 1059 // Test handshake/init chain 1060 1061 func TestHandshakeUpdatesValidators(t *testing.T) { 1062 val, _ := types.RandValidator(true, 10) 1063 vals := types.NewValidatorSet([]*types.Validator{val}) 1064 app := &initChainApp{vals: types.TM2PB.ValidatorUpdates(vals)} 1065 clientCreator := proxy.NewLocalClientCreator(app) 1066 1067 config := ResetConfig("handshake_test_") 1068 defer os.RemoveAll(config.RootDir) 1069 privVal := privval.LoadFilePV(config.PrivValidatorKeyFile(), config.PrivValidatorStateFile()) 1070 stateDB, state, store := stateAndStore(config, privVal.GetPubKey(), 0x0) 1071 1072 oldValAddr := state.Validators.Validators[0].Address 1073 1074 // now start the app using the handshake - it should sync 1075 genDoc, _ := sm.MakeGenesisDocFromFile(config.GenesisFile()) 1076 handshaker := NewHandshaker(stateDB, state, store, genDoc) 1077 proxyApp := proxy.NewAppConns(clientCreator) 1078 if err := proxyApp.Start(); err != nil { 1079 t.Fatalf("Error starting proxy app connections: %v", err) 1080 } 1081 defer proxyApp.Stop() 1082 if err := handshaker.Handshake(proxyApp); err != nil { 1083 t.Fatalf("Error on abci handshake: %v", err) 1084 } 1085 1086 // reload the state, check the validator set was updated 1087 state = sm.LoadState(stateDB) 1088 1089 newValAddr := state.Validators.Validators[0].Address 1090 expectValAddr := val.Address 1091 assert.NotEqual(t, oldValAddr, newValAddr) 1092 assert.Equal(t, newValAddr, expectValAddr) 1093 } 1094 1095 // returns the vals on InitChain 1096 type initChainApp struct { 1097 abci.BaseApplication 1098 vals []abci.ValidatorUpdate 1099 } 1100 1101 func (ica *initChainApp) InitChain(req abci.RequestInitChain) abci.ResponseInitChain { 1102 return abci.ResponseInitChain{ 1103 Validators: ica.vals, 1104 } 1105 }