From a1c20ce866f0383eb4b24fde04632965d210353a Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Tue, 9 Aug 2016 17:18:29 -0400 Subject: [PATCH] types: privVal.LastSignature. closes #247 --- consensus/replay_test.go | 75 ++++++++++++++++++++++++--- types/priv_validator.go | 109 +++++++++++++++++++++------------------ 2 files changed, 127 insertions(+), 57 deletions(-) diff --git a/consensus/replay_test.go b/consensus/replay_test.go index 46e862ed2..2e53f5199 100644 --- a/consensus/replay_test.go +++ b/consensus/replay_test.go @@ -7,6 +7,8 @@ import ( "time" . "github.com/tendermint/go-common" + "github.com/tendermint/go-events" + "github.com/tendermint/go-wire" "github.com/tendermint/tendermint/types" ) @@ -46,24 +48,83 @@ import ( ``` */ -var testLog = `{"time":"2016-04-03T11:23:54.387Z","msg":[3,{"duration":972835254,"height":1,"round":0,"step":1}]} +var testLog1 = `{"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"}]} +` + +// continuation; splitting allows us to test saving the privVal.LastSignature +// ... to test the case when we sign but crash before writing to the wal, +// we only run replay on testLog1 but stick this signature in the privVal.LastSignature after the proposal +var testLog2 = `{"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":""}]} +` + +// continuation; splitting allows us to test saving the privVal.LastSignature +var testLog3 = `{"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":""}]} ` -func TestReplayCatchup(t *testing.T) { +func TestReplayWithoutSig(t *testing.T) { + // write the needed wal to file + f, err := ioutil.TempFile(os.TempDir(), "replay_test_") + if err != nil { + panic(err) + } + _, err = f.WriteString(testLog1) + if err != nil { + panic(err) + } + f.Close() + + cs := fixedConsensusState() + + // we've already precommitted on the first block + // without replay catchup we would be halted here forever + cs.privValidator.LastHeight = 1 // first block + cs.privValidator.LastStep = 2 // prevote + + newBlockCh := subscribeToEvent(cs.evsw, "tester", types.EventStringNewBlock(), 0) + cs.evsw.AddListenerForEvent("tester", types.EventStringCompleteProposal(), func(data events.EventData) { + // Set LastSig + + // unmarshal log2 + var err error + var msg ConsensusLogMessage + wire.ReadJSON(&msg, []byte(testLog2), &err) + vote := msg.Msg.(msgInfo).Msg.(*VoteMessage) + if err != nil { + t.Fatalf("Error reading json data: %v", err) + } + + cs.privValidator.LastSignature = vote.Vote.Signature + }) + + // start timeout and receive routines + cs.startRoutines(0) + + // open wal and run catchup messages + openWAL(t, cs, f.Name()) + if err := cs.catchupReplay(cs.Height); err != nil { + panic(Fmt("Error on catchup replay %v", err)) + } + + after := time.After(time.Second * 15) + select { + case <-newBlockCh: + case <-after: + panic("Timed out waiting for new block") + } +} + +func TestReplayWithSig(t *testing.T) { // write the needed wal to file f, err := ioutil.TempFile(os.TempDir(), "replay_test_") if err != nil { panic(err) } - name := f.Name() - _, err = f.WriteString(testLog) + _, err = f.WriteString(testLog1 + testLog2 + testLog3) if err != nil { panic(err) } @@ -82,7 +143,7 @@ func TestReplayCatchup(t *testing.T) { cs.startRoutines(0) // open wal and run catchup messages - openWAL(t, cs, name) + openWAL(t, cs, f.Name()) if err := cs.catchupReplay(cs.Height); err != nil { panic(Fmt("Error on catchup replay %v", err)) } diff --git a/types/priv_validator.go b/types/priv_validator.go index 30fdfff76..e9046c5a4 100644 --- a/types/priv_validator.go +++ b/types/priv_validator.go @@ -35,11 +35,12 @@ func voteToStep(vote *Vote) int8 { } type PrivValidator struct { - Address []byte `json:"address"` - PubKey crypto.PubKey `json:"pub_key"` - LastHeight int `json:"last_height"` - LastRound int `json:"last_round"` - LastStep int8 `json:"last_step"` + Address []byte `json:"address"` + PubKey crypto.PubKey `json:"pub_key"` + LastHeight int `json:"last_height"` + LastRound int `json:"last_round"` + LastStep int8 `json:"last_step"` + LastSignature crypto.Signature `json:"last_signature"` // so we dont lose signatures // PrivKey should be empty if a Signer other than the default is being used. PrivKey crypto.PrivKey `json:"priv_key"` @@ -85,14 +86,15 @@ func GenPrivValidator() *PrivValidator { pubKey := crypto.PubKeyEd25519(*pubKeyBytes) privKey := crypto.PrivKeyEd25519(*privKeyBytes) return &PrivValidator{ - Address: pubKey.Address(), - PubKey: pubKey, - PrivKey: privKey, - LastHeight: 0, - LastRound: 0, - LastStep: stepNone, - filePath: "", - Signer: NewDefaultSigner(privKey), + Address: pubKey.Address(), + PubKey: pubKey, + PrivKey: privKey, + LastHeight: 0, + LastRound: 0, + LastStep: stepNone, + LastSignature: nil, + filePath: "", + Signer: NewDefaultSigner(privKey), } } @@ -152,53 +154,60 @@ func (privVal *PrivValidator) save() { func (privVal *PrivValidator) SignVote(chainID string, vote *Vote) error { privVal.mtx.Lock() defer privVal.mtx.Unlock() + signature, err := privVal.signBytesHRS(vote.Height, vote.Round, voteToStep(vote), SignBytes(chainID, vote)) + if err != nil { + return errors.New(Fmt("Error signing vote: %v", err)) + } + vote.Signature = signature.(crypto.SignatureEd25519) + return nil +} + +func (privVal *PrivValidator) SignProposal(chainID string, proposal *Proposal) error { + privVal.mtx.Lock() + defer privVal.mtx.Unlock() + signature, err := privVal.signBytesHRS(proposal.Height, proposal.Round, stepPropose, SignBytes(chainID, proposal)) + if err != nil { + return errors.New(Fmt("Error signing proposal: %v", err)) + } + proposal.Signature = signature.(crypto.SignatureEd25519) + return nil +} - // If height regression, panic - if privVal.LastHeight > vote.Height { - return errors.New("Height regression in SignVote") +// check if there's a regression. Else sign and write the hrs+signature to disk +func (privVal *PrivValidator) signBytesHRS(height, round int, step int8, signBytes []byte) (crypto.Signature, error) { + // If height regression, err + if privVal.LastHeight > height { + return nil, errors.New("Height regression") } // More cases for when the height matches - if privVal.LastHeight == vote.Height { - // If round regression, panic - if privVal.LastRound > vote.Round { - return errors.New("Round regression in SignVote") + if privVal.LastHeight == height { + // If round regression, err + if privVal.LastRound > round { + return nil, errors.New("Round regression") } - // If step regression, panic - if privVal.LastRound == vote.Round && privVal.LastStep > voteToStep(vote) { - return errors.New("Step regression in SignVote") + // If step regression, err + if privVal.LastRound == round { + if privVal.LastStep > step { + return nil, errors.New("Step regression") + } else if privVal.LastStep == step { + if privVal.LastSignature != nil { + return privVal.LastSignature, nil + } + } } } + // Sign + signature := privVal.Sign(signBytes) + // Persist height/round/step - privVal.LastHeight = vote.Height - privVal.LastRound = vote.Round - privVal.LastStep = voteToStep(vote) + privVal.LastHeight = height + privVal.LastRound = round + privVal.LastStep = step + privVal.LastSignature = signature privVal.save() - // Sign - vote.Signature = privVal.Sign(SignBytes(chainID, vote)).(crypto.SignatureEd25519) - return nil -} - -func (privVal *PrivValidator) SignProposal(chainID string, proposal *Proposal) error { - privVal.mtx.Lock() - defer privVal.mtx.Unlock() - if privVal.LastHeight < proposal.Height || - privVal.LastHeight == proposal.Height && privVal.LastRound < proposal.Round || - privVal.LastHeight == 0 && privVal.LastRound == 0 && privVal.LastStep == stepNone { - - // Persist height/round/step - privVal.LastHeight = proposal.Height - privVal.LastRound = proposal.Round - privVal.LastStep = stepPropose - privVal.save() - - // Sign - proposal.Signature = privVal.Sign(SignBytes(chainID, proposal)).(crypto.SignatureEd25519) - return nil - } else { - return errors.New(fmt.Sprintf("Attempt of duplicate signing of proposal: Height %v, Round %v", proposal.Height, proposal.Round)) - } + return signature, nil } func (privVal *PrivValidator) String() string {