|
|
@ -4,6 +4,7 @@ import ( |
|
|
|
"fmt" |
|
|
|
"io/ioutil" |
|
|
|
"os" |
|
|
|
"path" |
|
|
|
"strings" |
|
|
|
"testing" |
|
|
|
"time" |
|
|
@ -13,62 +14,61 @@ import ( |
|
|
|
"github.com/tendermint/tendermint/types" |
|
|
|
) |
|
|
|
|
|
|
|
/* |
|
|
|
The easiest way to generate this data is to copy ~/.tendermint_test/somedir/* to ~/.tendermint |
|
|
|
and to run a local node. |
|
|
|
Be sure to set the db to "leveldb" to create a cswal file in ~/.tendermint/data/cswal. |
|
|
|
var data_dir = path.Join(GoPath, "src/github.com/tendermint/tendermint/consensus", "test_data") |
|
|
|
|
|
|
|
If you need to change the signatures, you can use a script as follows: |
|
|
|
The privBytes comes from config/tendermint_test/... |
|
|
|
// the priv validator changes step at these lines for a block with 1 val and 1 part
|
|
|
|
var baseStepChanges = []int{2, 5, 7} |
|
|
|
|
|
|
|
``` |
|
|
|
package main |
|
|
|
// 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{2, 7, 9}), // small block with txs across 3 smaller block parts
|
|
|
|
} |
|
|
|
|
|
|
|
import ( |
|
|
|
"encoding/hex" |
|
|
|
"fmt" |
|
|
|
type testCase struct { |
|
|
|
name string |
|
|
|
log string //full cswal
|
|
|
|
stepMap map[int]int8 // map lines of log to privval step
|
|
|
|
|
|
|
|
"github.com/tendermint/go-crypto" |
|
|
|
) |
|
|
|
proposeLine int |
|
|
|
prevoteLine int |
|
|
|
precommitLine int |
|
|
|
} |
|
|
|
|
|
|
|
func main() { |
|
|
|
signBytes, err := hex.DecodeString("7B22636861696E5F6964223A2274656E6465726D696E745F74657374222C22766F7465223A7B22626C6F636B5F68617368223A2242453544373939433846353044354645383533364334333932464443384537423342313830373638222C22626C6F636B5F70617274735F686561646572223A506172745365747B543A31204236323237323535464632307D2C22686569676874223A312C22726F756E64223A302C2274797065223A327D7D") |
|
|
|
if err != nil { |
|
|
|
panic(err) |
|
|
|
} |
|
|
|
privBytes, err := hex.DecodeString("27F82582AEFAE7AB151CFB01C48BB6C1A0DA78F9BDDA979A9F70A84D074EB07D3B3069C422E19688B45CBFAE7BB009FC0FA1B1EA86593519318B7214853803C8") |
|
|
|
if err != nil { |
|
|
|
panic(err) |
|
|
|
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], |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
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) |
|
|
|
} |
|
|
|
privKey := crypto.PrivKeyEd25519{} |
|
|
|
copy(privKey[:], privBytes) |
|
|
|
signature := privKey.Sign(signBytes) |
|
|
|
signatureEd25519 := signature.(crypto.SignatureEd25519) |
|
|
|
fmt.Printf("Signature Bytes: %X\n", signatureEd25519[:]) |
|
|
|
} |
|
|
|
``` |
|
|
|
*/ |
|
|
|
|
|
|
|
var testLog = `{"time":"2016-04-03T11:23:54.387Z","msg":[3,{"duration":972835254,"height":1,"round":0,"step":1}]} |
|
|
|
{"time":"2016-04-03T11:23:54.388Z","msg":[1,{"height":1,"round":0,"step":"RoundStepPropose"}]} |
|
|
|
{"time":"2016-04-03T11:23:54.388Z","msg":[2,{"msg":[17,{"Proposal":{"height":1,"round":0,"block_parts_header":{"total":1,"hash":"3BA1E90CB868DA6B4FD7F3589826EC461E9EB4EF"},"pol_round":-1,"signature":"3A2ECD5023B21EC144EC16CFF1B992A4321317B83EEDD8969FDFEA6EB7BF4389F38DDA3E7BB109D63A07491C16277A197B241CF1F05F5E485C59882ECACD9E07"}}],"peer_key":""}]} |
|
|
|
{"time":"2016-04-03T11:23:54.389Z","msg":[2,{"msg":[19,{"Height":1,"Round":0,"Part":{"index":0,"bytes":"0101010F74656E6465726D696E745F7465737401011441D59F4B718AC00000000000000114C4B01D3810579550997AC5641E759E20D99B51C10001000100","proof":{"aunts":[]}}}],"peer_key":""}]} |
|
|
|
{"time":"2016-04-03T11:23:54.390Z","msg":[1,{"height":1,"round":0,"step":"RoundStepPrevote"}]} |
|
|
|
{"time":"2016-04-03T11:23:54.390Z","msg":[2,{"msg":[20,{"ValidatorIndex":0,"Vote":{"height":1,"round":0,"type":1,"block_hash":"4291966B8A9DFBA00AEC7C700F2718E61DF4331D","block_parts_header":{"total":1,"hash":"3BA1E90CB868DA6B4FD7F3589826EC461E9EB4EF"},"signature":"47D2A75A4E2F15DB1F0D1B656AC0637AF9AADDFEB6A156874F6553C73895E5D5DC948DBAEF15E61276C5342D0E638DFCB77C971CD282096EA8735A564A90F008"}}],"peer_key":""}]} |
|
|
|
{"time":"2016-04-03T11:23:54.392Z","msg":[1,{"height":1,"round":0,"step":"RoundStepPrecommit"}]} |
|
|
|
{"time":"2016-04-03T11:23:54.392Z","msg":[2,{"msg":[20,{"ValidatorIndex":0,"Vote":{"height":1,"round":0,"type":2,"block_hash":"4291966B8A9DFBA00AEC7C700F2718E61DF4331D","block_parts_header":{"total":1,"hash":"3BA1E90CB868DA6B4FD7F3589826EC461E9EB4EF"},"signature":"39147DA595F08B73CF8C899967C8403B5872FD9042FFA4E239159E0B6C5D9665C9CA81D766EACA2AE658872F94C2FCD1E34BF51859CD5B274DA8512BACE4B50D"}}],"peer_key":""}]} |
|
|
|
` |
|
|
|
|
|
|
|
// map lines in the above wal to privVal step
|
|
|
|
var mapPrivValStep = map[int]int8{ |
|
|
|
0: 0, |
|
|
|
1: 0, |
|
|
|
2: 1, |
|
|
|
3: 1, |
|
|
|
4: 1, |
|
|
|
5: 2, |
|
|
|
6: 2, |
|
|
|
7: 3, |
|
|
|
return m |
|
|
|
} |
|
|
|
|
|
|
|
func readWAL(p string) string { |
|
|
|
b, err := ioutil.ReadFile(p) |
|
|
|
if err != nil { |
|
|
|
panic(err) |
|
|
|
} |
|
|
|
return string(b) |
|
|
|
} |
|
|
|
|
|
|
|
func writeWAL(log string) string { |
|
|
@ -88,27 +88,29 @@ func writeWAL(log string) string { |
|
|
|
return name |
|
|
|
} |
|
|
|
|
|
|
|
func waitForBlock(newBlockCh chan interface{}) { |
|
|
|
func waitForBlock(newBlockCh chan interface{}, thisCase *testCase, i int) { |
|
|
|
after := time.After(time.Second * 10) |
|
|
|
select { |
|
|
|
case <-newBlockCh: |
|
|
|
case <-after: |
|
|
|
panic("Timed out waiting for new block") |
|
|
|
panic(Fmt("Timed out waiting for new block for case '%s' line %d", thisCase.name, i)) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
func runReplayTest(t *testing.T, cs *ConsensusState, fileName string, newBlockCh chan interface{}) { |
|
|
|
func runReplayTest(t *testing.T, cs *ConsensusState, fileName string, newBlockCh chan interface{}, |
|
|
|
thisCase *testCase, i int) { |
|
|
|
|
|
|
|
cs.config.Set("cswal", fileName) |
|
|
|
cs.Start() |
|
|
|
// 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
|
|
|
|
waitForBlock(newBlockCh) |
|
|
|
waitForBlock(newBlockCh, thisCase, i) |
|
|
|
cs.Stop() |
|
|
|
} |
|
|
|
|
|
|
|
func setupReplayTest(nLines int, crashAfter bool) (*ConsensusState, chan interface{}, string, string) { |
|
|
|
func setupReplayTest(thisCase *testCase, nLines int, crashAfter bool) (*ConsensusState, chan interface{}, string, string) { |
|
|
|
fmt.Println("-------------------------------------") |
|
|
|
log.Notice(Fmt("Starting replay test of %d lines of WAL (crash before write)", nLines)) |
|
|
|
|
|
|
@ -117,17 +119,17 @@ func setupReplayTest(nLines int, crashAfter bool) (*ConsensusState, chan interfa |
|
|
|
lineStep -= 1 |
|
|
|
} |
|
|
|
|
|
|
|
split := strings.Split(testLog, "\n") |
|
|
|
split := strings.Split(thisCase.log, "\n") |
|
|
|
lastMsg := split[nLines] |
|
|
|
|
|
|
|
// we write those lines up to (not including) one with the signature
|
|
|
|
fileName := writeWAL(strings.Join(split[:nLines], "\n") + "\n") |
|
|
|
|
|
|
|
cs := fixedConsensusState() |
|
|
|
cs := fixedConsensusStateDummy() |
|
|
|
|
|
|
|
// set the last step according to when we crashed vs the wal
|
|
|
|
cs.privValidator.LastHeight = 1 // first block
|
|
|
|
cs.privValidator.LastStep = mapPrivValStep[lineStep] |
|
|
|
cs.privValidator.LastStep = thisCase.stepMap[lineStep] |
|
|
|
|
|
|
|
fmt.Println("LAST STEP", cs.privValidator.LastStep) |
|
|
|
|
|
|
@ -141,10 +143,12 @@ func setupReplayTest(nLines int, crashAfter bool) (*ConsensusState, chan interfa |
|
|
|
// as if the log was written after signing, before the crash
|
|
|
|
|
|
|
|
func TestReplayCrashAfterWrite(t *testing.T) { |
|
|
|
split := strings.Split(testLog, "\n") |
|
|
|
for i := 0; i < len(split)-1; i++ { |
|
|
|
cs, newBlockCh, _, f := setupReplayTest(i+1, true) |
|
|
|
runReplayTest(t, cs, f, newBlockCh) |
|
|
|
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) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
@ -153,50 +157,59 @@ func TestReplayCrashAfterWrite(t *testing.T) { |
|
|
|
// This relies on privValidator.LastSignature being set
|
|
|
|
|
|
|
|
func TestReplayCrashBeforeWritePropose(t *testing.T) { |
|
|
|
cs, newBlockCh, proposalMsg, f := setupReplayTest(2, false) // propose
|
|
|
|
// Set LastSig
|
|
|
|
var err error |
|
|
|
var msg ConsensusLogMessage |
|
|
|
wire.ReadJSON(&msg, []byte(proposalMsg), &err) |
|
|
|
proposal := msg.Msg.(msgInfo).Msg.(*ProposalMessage) |
|
|
|
if err != nil { |
|
|
|
t.Fatalf("Error reading json data: %v", err) |
|
|
|
} |
|
|
|
cs.privValidator.LastSignBytes = types.SignBytes(cs.state.ChainID, proposal.Proposal) |
|
|
|
cs.privValidator.LastSignature = proposal.Proposal.Signature |
|
|
|
runReplayTest(t, cs, f, newBlockCh) |
|
|
|
} |
|
|
|
|
|
|
|
func TestReplayCrashBeforeWritePrevote(t *testing.T) { |
|
|
|
cs, newBlockCh, voteMsg, f := setupReplayTest(5, false) // prevote
|
|
|
|
types.AddListenerForEvent(cs.evsw, "tester", types.EventStringCompleteProposal(), func(data types.TMEventData) { |
|
|
|
for _, thisCase := range testCases { |
|
|
|
lineNum := thisCase.proposeLine |
|
|
|
cs, newBlockCh, proposalMsg, f := setupReplayTest(thisCase, lineNum, false) // propose
|
|
|
|
// Set LastSig
|
|
|
|
var err error |
|
|
|
var msg ConsensusLogMessage |
|
|
|
wire.ReadJSON(&msg, []byte(voteMsg), &err) |
|
|
|
vote := msg.Msg.(msgInfo).Msg.(*VoteMessage) |
|
|
|
wire.ReadJSON(&msg, []byte(proposalMsg), &err) |
|
|
|
proposal := msg.Msg.(msgInfo).Msg.(*ProposalMessage) |
|
|
|
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) |
|
|
|
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 := thisCase.prevoteLine |
|
|
|
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) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
func TestReplayCrashBeforeWritePrecommit(t *testing.T) { |
|
|
|
cs, newBlockCh, voteMsg, f := setupReplayTest(7, 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) |
|
|
|
for _, thisCase := range testCases { |
|
|
|
lineNum := thisCase.precommitLine |
|
|
|
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) |
|
|
|
} |
|
|
|
} |