2016-01-18 15:57:57 -05:00
|
|
|
package consensus
|
|
|
|
|
|
|
|
import (
|
2016-08-14 12:31:24 -04:00
|
|
|
"fmt"
|
2016-01-18 15:57:57 -05:00
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
2016-10-11 11:44:07 -04:00
|
|
|
"path"
|
2016-08-14 12:31:24 -04:00
|
|
|
"strings"
|
2016-01-18 15:57:57 -05:00
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
2016-07-11 21:10:05 -04:00
|
|
|
. "github.com/tendermint/go-common"
|
2016-08-09 17:18:29 -04:00
|
|
|
"github.com/tendermint/go-wire"
|
2016-01-18 15:57:57 -05:00
|
|
|
"github.com/tendermint/tendermint/types"
|
|
|
|
)
|
|
|
|
|
2016-10-11 11:44:07 -04:00
|
|
|
var data_dir = "test_data" // TODO
|
2016-04-03 04:51:44 -07:00
|
|
|
|
2016-10-11 11:44:07 -04:00
|
|
|
type testCase struct {
|
|
|
|
name string
|
|
|
|
log string //full cswal
|
|
|
|
stepMap map[int]int8 // map lines of log to privval step
|
|
|
|
}
|
2016-04-02 09:10:16 -07:00
|
|
|
|
2016-10-11 11:44:07 -04:00
|
|
|
// mapping for one validator and blocks with one part
|
|
|
|
var baseStepMap = map[int]int8{
|
2016-08-14 12:31:24 -04:00
|
|
|
0: 0,
|
|
|
|
1: 0,
|
|
|
|
2: 1,
|
|
|
|
3: 1,
|
|
|
|
4: 1,
|
|
|
|
5: 2,
|
|
|
|
6: 2,
|
|
|
|
7: 3,
|
|
|
|
}
|
|
|
|
|
2016-10-11 11:44:07 -04:00
|
|
|
var testCases = []*testCase{
|
|
|
|
&testCase{
|
|
|
|
name: "empty_block",
|
|
|
|
stepMap: baseStepMap,
|
|
|
|
},
|
|
|
|
&testCase{
|
|
|
|
name: "small_block",
|
|
|
|
stepMap: baseStepMap,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
// populate test logs by reading files
|
|
|
|
func init() {
|
|
|
|
for _, c := range testCases {
|
|
|
|
c.log = readWAL(path.Join(data_dir, c.name+".cswal"))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func readWAL(p string) string {
|
|
|
|
b, err := ioutil.ReadFile(p)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
return string(b)
|
|
|
|
}
|
|
|
|
|
2016-08-14 12:31:24 -04:00
|
|
|
func writeWAL(log string) string {
|
|
|
|
fmt.Println("writing", log)
|
2016-08-09 17:18:29 -04:00
|
|
|
// write the needed wal to file
|
|
|
|
f, err := ioutil.TempFile(os.TempDir(), "replay_test_")
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
2016-08-14 12:31:24 -04:00
|
|
|
|
|
|
|
_, err = f.WriteString(log)
|
2016-08-09 17:18:29 -04:00
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
2016-08-14 12:31:24 -04:00
|
|
|
name := f.Name()
|
2016-08-09 17:18:29 -04:00
|
|
|
f.Close()
|
2016-08-14 12:31:24 -04:00
|
|
|
return name
|
|
|
|
}
|
2016-08-09 17:18:29 -04:00
|
|
|
|
2016-10-11 11:44:07 -04:00
|
|
|
func waitForBlock(newBlockCh chan interface{}, thisCase *testCase, i int) {
|
2016-08-14 12:31:24 -04:00
|
|
|
after := time.After(time.Second * 10)
|
2016-08-09 17:18:29 -04:00
|
|
|
select {
|
|
|
|
case <-newBlockCh:
|
|
|
|
case <-after:
|
2016-10-11 11:44:07 -04:00
|
|
|
panic(Fmt("Timed out waiting for new block for case '%s' line %d", thisCase.name, i))
|
2016-08-09 17:18:29 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-11 11:44:07 -04:00
|
|
|
func runReplayTest(t *testing.T, cs *ConsensusState, fileName string, newBlockCh chan interface{},
|
|
|
|
thisCase *testCase, i int) {
|
|
|
|
|
2016-08-17 23:08:43 -04:00
|
|
|
cs.config.Set("cswal", fileName)
|
|
|
|
cs.Start()
|
2016-08-23 11:33:18 -04: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 11:44:07 -04:00
|
|
|
waitForBlock(newBlockCh, thisCase, i)
|
2016-08-17 23:08:43 -04:00
|
|
|
cs.Stop()
|
2016-08-14 12:31:24 -04:00
|
|
|
}
|
|
|
|
|
2016-10-11 11:44:07 -04:00
|
|
|
func setupReplayTest(thisCase *testCase, nLines int, crashAfter bool) (*ConsensusState, chan interface{}, string, string) {
|
2016-08-14 12:31:24 -04:00
|
|
|
fmt.Println("-------------------------------------")
|
|
|
|
log.Notice(Fmt("Starting replay test of %d lines of WAL (crash before write)", nLines))
|
|
|
|
|
|
|
|
lineStep := nLines
|
|
|
|
if crashAfter {
|
|
|
|
lineStep -= 1
|
2016-01-18 15:57:57 -05:00
|
|
|
}
|
2016-08-14 12:31:24 -04:00
|
|
|
|
2016-10-11 11:44:07 -04:00
|
|
|
split := strings.Split(thisCase.log, "\n")
|
2016-08-14 12:31:24 -04:00
|
|
|
lastMsg := split[nLines]
|
|
|
|
|
|
|
|
// we write those lines up to (not including) one with the signature
|
|
|
|
fileName := writeWAL(strings.Join(split[:nLines], "\n") + "\n")
|
2016-01-18 15:57:57 -05:00
|
|
|
|
|
|
|
cs := fixedConsensusState()
|
|
|
|
|
2016-08-17 23:08:43 -04:00
|
|
|
// set the last step according to when we crashed vs the wal
|
2016-01-18 15:57:57 -05:00
|
|
|
cs.privValidator.LastHeight = 1 // first block
|
2016-10-11 11:44:07 -04:00
|
|
|
cs.privValidator.LastStep = thisCase.stepMap[lineStep]
|
2016-01-18 15:57:57 -05:00
|
|
|
|
2016-08-14 12:31:24 -04:00
|
|
|
fmt.Println("LAST STEP", cs.privValidator.LastStep)
|
2016-01-18 15:57:57 -05:00
|
|
|
|
2016-08-14 12:31:24 -04:00
|
|
|
newBlockCh := subscribeToEvent(cs.evsw, "tester", types.EventStringNewBlock(), 1)
|
2016-01-18 15:57:57 -05:00
|
|
|
|
2016-08-14 12:31:24 -04:00
|
|
|
return cs, newBlockCh, lastMsg, fileName
|
2016-01-18 15:57:57 -05:00
|
|
|
}
|
|
|
|
|
2016-08-14 12:31:24 -04:00
|
|
|
//-----------------------------------------------
|
|
|
|
// 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 11:44:07 -04:00
|
|
|
for _, thisCase := range testCases {
|
|
|
|
split := strings.Split(thisCase.log, "\n")
|
|
|
|
for i := 0; i < len(split)-1; i++ {
|
|
|
|
cs, newBlockCh, _, f := setupReplayTest(thisCase, i+1, true)
|
|
|
|
runReplayTest(t, cs, f, newBlockCh, thisCase, i+1)
|
|
|
|
}
|
2016-08-14 12:31:24 -04: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 11:44:07 -04:00
|
|
|
for _, thisCase := range testCases {
|
|
|
|
lineNum := 2
|
|
|
|
cs, newBlockCh, proposalMsg, f := setupReplayTest(thisCase, lineNum, false) // propose
|
2016-08-14 12:31:24 -04:00
|
|
|
// Set LastSig
|
|
|
|
var err error
|
|
|
|
var msg ConsensusLogMessage
|
2016-10-11 11:44:07 -04:00
|
|
|
wire.ReadJSON(&msg, []byte(proposalMsg), &err)
|
|
|
|
proposal := msg.Msg.(msgInfo).Msg.(*ProposalMessage)
|
2016-08-14 12:31:24 -04:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Error reading json data: %v", err)
|
|
|
|
}
|
2016-10-11 11:44:07 -04:00
|
|
|
cs.privValidator.LastSignBytes = types.SignBytes(cs.state.ChainID, proposal.Proposal)
|
|
|
|
cs.privValidator.LastSignature = proposal.Proposal.Signature
|
|
|
|
runReplayTest(t, cs, f, newBlockCh, thisCase, lineNum)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestReplayCrashBeforeWritePrevote(t *testing.T) {
|
|
|
|
for _, thisCase := range testCases {
|
|
|
|
lineNum := 5
|
|
|
|
cs, newBlockCh, voteMsg, f := setupReplayTest(thisCase, lineNum, false) // prevote
|
|
|
|
types.AddListenerForEvent(cs.evsw, "tester", types.EventStringCompleteProposal(), func(data types.TMEventData) {
|
|
|
|
// Set LastSig
|
|
|
|
var err error
|
|
|
|
var msg ConsensusLogMessage
|
|
|
|
wire.ReadJSON(&msg, []byte(voteMsg), &err)
|
|
|
|
vote := msg.Msg.(msgInfo).Msg.(*VoteMessage)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Error reading json data: %v", err)
|
|
|
|
}
|
|
|
|
cs.privValidator.LastSignBytes = types.SignBytes(cs.state.ChainID, vote.Vote)
|
|
|
|
cs.privValidator.LastSignature = vote.Vote.Signature
|
|
|
|
})
|
|
|
|
runReplayTest(t, cs, f, newBlockCh, thisCase, lineNum)
|
|
|
|
}
|
2016-08-14 12:31:24 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestReplayCrashBeforeWritePrecommit(t *testing.T) {
|
2016-10-11 11:44:07 -04:00
|
|
|
for _, thisCase := range testCases {
|
|
|
|
lineNum := 7
|
|
|
|
cs, newBlockCh, voteMsg, f := setupReplayTest(thisCase, lineNum, false) // precommit
|
|
|
|
types.AddListenerForEvent(cs.evsw, "tester", types.EventStringPolka(), func(data types.TMEventData) {
|
|
|
|
// Set LastSig
|
|
|
|
var err error
|
|
|
|
var msg ConsensusLogMessage
|
|
|
|
wire.ReadJSON(&msg, []byte(voteMsg), &err)
|
|
|
|
vote := msg.Msg.(msgInfo).Msg.(*VoteMessage)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Error reading json data: %v", err)
|
|
|
|
}
|
|
|
|
cs.privValidator.LastSignBytes = types.SignBytes(cs.state.ChainID, vote.Vote)
|
|
|
|
cs.privValidator.LastSignature = vote.Vote.Signature
|
|
|
|
})
|
|
|
|
runReplayTest(t, cs, f, newBlockCh, thisCase, lineNum)
|
|
|
|
}
|
2016-08-14 12:31:24 -04:00
|
|
|
}
|