tendermint/consensus/replay_test.go

223 lines
6.9 KiB
Go
Raw Normal View History

2016-01-18 12:57:57 -08:00
package consensus
import (
"fmt"
2016-01-18 12:57:57 -08:00
"io/ioutil"
"os"
2016-10-11 08:44:07 -07:00
"path"
"strings"
2016-01-18 12:57:57 -08:00
"testing"
"time"
2017-01-12 07:58:44 -08:00
"github.com/tendermint/tendermint/config/tendermint_test"
2016-07-11 18:10:05 -07:00
. "github.com/tendermint/go-common"
"github.com/tendermint/go-wire"
2016-01-18 12:57:57 -08:00
"github.com/tendermint/tendermint/types"
)
2017-01-12 07:58:44 -08:00
func init() {
config = tendermint_test.ResetConfig("consensus_replay_test")
}
// TODO: these tests ensure we can always recover from any state of the wal,
2016-12-22 18:51:58 -08:00
// assuming it comes with a correct related state for the priv_validator.json.
// It would be better to verify explicitly which states we can recover from without the wal
// and which ones we need the wal for - then we'd also be able to only flush the
// wal writer when we need to, instead of with every message.
2016-10-11 09:55:04 -07:00
var data_dir = path.Join(GoPath, "src/github.com/tendermint/tendermint/consensus", "test_data")
// the priv validator changes step at these lines for a block with 1 val and 1 part
2016-10-28 15:01:14 -07:00
var baseStepChanges = []int{3, 6, 8}
2016-10-11 09:55:04 -07:00
// test recovery from each line in each testCase
var testCases = []*testCase{
newTestCase("empty_block", baseStepChanges), // empty block (has 1 block part)
newTestCase("small_block1", baseStepChanges), // small block with txs in 1 block part
newTestCase("small_block2", []int{3, 10, 12}), // small block with txs across 5 smaller block parts
2016-10-11 09:55:04 -07:00
}
2016-04-03 04:51:44 -07:00
2016-10-11 08:44:07 -07:00
type testCase struct {
name string
2016-10-30 03:55:27 -07:00
log string //full cs wal
2016-10-11 08:44:07 -07:00
stepMap map[int]int8 // map lines of log to privval step
2016-04-02 09:10:16 -07:00
2016-10-11 09:55:04 -07:00
proposeLine int
prevoteLine int
precommitLine int
}
2016-10-11 09:55:04 -07:00
func newTestCase(name string, stepChanges []int) *testCase {
if len(stepChanges) != 3 {
panic(Fmt("a full wal has 3 step changes! Got array %v", stepChanges))
}
return &testCase{
name: name,
log: readWAL(path.Join(data_dir, name+".cswal")),
stepMap: newMapFromChanges(stepChanges),
proposeLine: stepChanges[0],
prevoteLine: stepChanges[1],
precommitLine: stepChanges[2],
}
2016-10-11 08:44:07 -07:00
}
2016-10-11 09:55:04 -07:00
func newMapFromChanges(changes []int) map[int]int8 {
changes = append(changes, changes[2]+1) // so we add the last step change to the map
m := make(map[int]int8)
var count int
for changeNum, nextChange := range changes {
for ; count < nextChange; count++ {
m[count] = int8(changeNum)
}
2016-10-11 08:44:07 -07:00
}
2016-10-11 09:55:04 -07:00
return m
2016-10-11 08:44:07 -07:00
}
func readWAL(p string) string {
b, err := ioutil.ReadFile(p)
if err != nil {
panic(err)
}
return string(b)
}
2016-10-30 03:55:27 -07:00
func writeWAL(walMsgs string) string {
tempDir := os.TempDir()
walDir := tempDir + "/wal" + RandStr(12)
// Create WAL directory
err := EnsureDir(walDir, 0700)
if err != nil {
panic(err)
}
2016-10-30 03:55:27 -07:00
// Write the needed WAL to file
err = WriteFile(walDir+"/wal", []byte(walMsgs), 0600)
if err != nil {
panic(err)
}
2016-10-30 03:55:27 -07:00
return walDir
}
2016-10-11 08:44:07 -07:00
func waitForBlock(newBlockCh chan interface{}, thisCase *testCase, i int) {
after := time.After(time.Second * 10)
select {
case <-newBlockCh:
case <-after:
2016-10-11 08:44:07 -07:00
panic(Fmt("Timed out waiting for new block for case '%s' line %d", thisCase.name, i))
}
}
2016-10-30 03:55:27 -07:00
func runReplayTest(t *testing.T, cs *ConsensusState, walDir string, newBlockCh chan interface{},
2016-10-11 08:44:07 -07:00
thisCase *testCase, i int) {
2016-10-30 03:55:27 -07:00
cs.config.Set("cs_wal_dir", walDir)
2016-08-17 20:08:43 -07:00
cs.Start()
2016-08-23 08:33:18 -07:00
// Wait to make a new block.
// This is just a signal that we haven't halted; its not something contained in the WAL itself.
// Assuming the consensus state is running, replay of any WAL, including the empty one,
// should eventually be followed by a new block, or else something is wrong
2016-10-11 08:44:07 -07:00
waitForBlock(newBlockCh, thisCase, i)
2016-08-17 20:08:43 -07:00
cs.Stop()
2016-10-28 15:01:14 -07:00
cs.Wait()
}
2016-06-26 12:33:11 -07:00
func toPV(pv PrivValidator) *types.PrivValidator {
return pv.(*types.PrivValidator)
}
2016-10-11 08:44:07 -07:00
func setupReplayTest(thisCase *testCase, nLines int, crashAfter bool) (*ConsensusState, chan interface{}, string, string) {
fmt.Println("-------------------------------------")
2016-12-06 01:16:13 -08:00
log.Notice(Fmt("Starting replay test %v (of %d lines of WAL). Crash after = %v", thisCase.name, nLines, crashAfter))
lineStep := nLines
if crashAfter {
lineStep -= 1
2016-01-18 12:57:57 -08:00
}
2016-10-11 08:44:07 -07:00
split := strings.Split(thisCase.log, "\n")
lastMsg := split[nLines]
// we write those lines up to (not including) one with the signature
2016-10-30 03:55:27 -07:00
walDir := writeWAL(strings.Join(split[:nLines], "\n") + "\n")
2016-01-18 12:57:57 -08:00
2016-10-11 09:51:48 -07:00
cs := fixedConsensusStateDummy()
2016-01-18 12:57:57 -08:00
2016-08-17 20:08:43 -07:00
// set the last step according to when we crashed vs the wal
2016-06-26 12:33:11 -07:00
toPV(cs.privValidator).LastHeight = 1 // first block
toPV(cs.privValidator).LastStep = thisCase.stepMap[lineStep]
2016-01-18 12:57:57 -08:00
2016-06-26 12:33:11 -07:00
log.Warn("setupReplayTest", "LastStep", toPV(cs.privValidator).LastStep)
2016-01-18 12:57:57 -08:00
newBlockCh := subscribeToEvent(cs.evsw, "tester", types.EventStringNewBlock(), 1)
2016-01-18 12:57:57 -08:00
2016-10-30 03:55:27 -07:00
return cs, newBlockCh, lastMsg, walDir
2016-01-18 12:57:57 -08:00
}
2016-12-22 18:51:58 -08:00
func readTimedWALMessage(t *testing.T, walMsg string) TimedWALMessage {
var err error
var msg TimedWALMessage
wire.ReadJSON(&msg, []byte(walMsg), &err)
if err != nil {
t.Fatalf("Error reading json data: %v", err)
}
return msg
}
//-----------------------------------------------
// Test the log at every iteration, and set the privVal last step
// as if the log was written after signing, before the crash
func TestReplayCrashAfterWrite(t *testing.T) {
2016-10-11 08:44:07 -07:00
for _, thisCase := range testCases {
split := strings.Split(thisCase.log, "\n")
for i := 0; i < len(split)-1; i++ {
2016-10-30 03:55:27 -07:00
cs, newBlockCh, _, walDir := setupReplayTest(thisCase, i+1, true)
runReplayTest(t, cs, walDir, newBlockCh, thisCase, i+1)
2016-10-11 08:44:07 -07:00
}
}
}
//-----------------------------------------------
// Test the log as if we crashed after signing but before writing.
// This relies on privValidator.LastSignature being set
func TestReplayCrashBeforeWritePropose(t *testing.T) {
2016-10-11 08:44:07 -07:00
for _, thisCase := range testCases {
2016-10-11 09:55:04 -07:00
lineNum := thisCase.proposeLine
2016-12-22 18:51:58 -08:00
// setup replay test where last message is a proposal
cs, newBlockCh, proposalMsg, walDir := setupReplayTest(thisCase, lineNum, false)
msg := readTimedWALMessage(t, proposalMsg)
2016-10-11 08:44:07 -07:00
proposal := msg.Msg.(msgInfo).Msg.(*ProposalMessage)
// Set LastSig
2016-06-26 12:33:11 -07:00
toPV(cs.privValidator).LastSignBytes = types.SignBytes(cs.state.ChainID, proposal.Proposal)
toPV(cs.privValidator).LastSignature = proposal.Proposal.Signature
2016-10-30 03:55:27 -07:00
runReplayTest(t, cs, walDir, newBlockCh, thisCase, lineNum)
2016-10-11 08:44:07 -07:00
}
}
func TestReplayCrashBeforeWritePrevote(t *testing.T) {
for _, thisCase := range testCases {
testReplayCrashBeforeWriteVote(t, thisCase, thisCase.prevoteLine, types.EventStringCompleteProposal())
2016-10-11 08:44:07 -07:00
}
}
func TestReplayCrashBeforeWritePrecommit(t *testing.T) {
2016-10-11 08:44:07 -07:00
for _, thisCase := range testCases {
testReplayCrashBeforeWriteVote(t, thisCase, thisCase.precommitLine, types.EventStringPolka())
2016-10-11 08:44:07 -07:00
}
}
func testReplayCrashBeforeWriteVote(t *testing.T, thisCase *testCase, lineNum int, eventString string) {
2016-12-22 18:51:58 -08:00
// setup replay test where last message is a vote
cs, newBlockCh, voteMsg, walDir := setupReplayTest(thisCase, lineNum, false)
types.AddListenerForEvent(cs.evsw, "tester", eventString, func(data types.TMEventData) {
2016-12-22 18:51:58 -08:00
msg := readTimedWALMessage(t, voteMsg)
vote := msg.Msg.(msgInfo).Msg.(*VoteMessage)
// Set LastSig
toPV(cs.privValidator).LastSignBytes = types.SignBytes(cs.state.ChainID, vote.Vote)
toPV(cs.privValidator).LastSignature = vote.Vote.Signature
})
runReplayTest(t, cs, walDir, newBlockCh, thisCase, lineNum)
}