// Copyright 2017 The go-ethereum Authors // This file is part of the go-ethereum library. // // The go-ethereum library is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // The go-ethereum library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . package core import ( "math/big" "reflect" "testing" "github.com/ethereum/go-ethereum/consensus/istanbul" istanbulcommon "github.com/ethereum/go-ethereum/consensus/istanbul/common" ibfttypes "github.com/ethereum/go-ethereum/consensus/istanbul/ibft/types" ) func newTestPreprepare(v *istanbul.View) *istanbul.Preprepare { return &istanbul.Preprepare{ View: v, Proposal: newTestProposal(), } } func TestHandlePreprepare(t *testing.T) { N := uint64(4) // replica 0 is the proposer, it will send messages to others F := uint64(1) // F does not affect tests testCases := []struct { system *testSystem expectedRequest istanbul.Proposal expectedErr error existingBlock bool }{ { // normal case func() *testSystem { sys := NewTestSystemWithBackend(N, F) for i, backend := range sys.backends { c := backend.engine c.valSet = backend.peers if i != 0 { c.state = ibfttypes.StateAcceptRequest } } return sys }(), newTestProposal(), nil, false, }, { // future message func() *testSystem { sys := NewTestSystemWithBackend(N, F) for i, backend := range sys.backends { c := backend.engine c.valSet = backend.peers if i != 0 { c.state = ibfttypes.StateAcceptRequest // hack: force set subject that future message can be simulated c.current = newTestRoundState( &istanbul.View{ Round: big.NewInt(0), Sequence: big.NewInt(0), }, c.valSet, ) } else { c.current.SetSequence(big.NewInt(10)) } } return sys }(), makeBlock(1), istanbulcommon.ErrFutureMessage, false, }, { // non-proposer func() *testSystem { sys := NewTestSystemWithBackend(N, F) // force remove replica 0, let replica 1 be the proposer sys.backends = sys.backends[1:] for i, backend := range sys.backends { c := backend.engine c.valSet = backend.peers if i != 0 { // replica 0 is the proposer c.state = ibfttypes.StatePreprepared } } return sys }(), makeBlock(1), istanbulcommon.ErrNotFromProposer, false, }, { // errOldMessage func() *testSystem { sys := NewTestSystemWithBackend(N, F) for i, backend := range sys.backends { c := backend.engine c.valSet = backend.peers if i != 0 { c.state = ibfttypes.StatePreprepared c.current.SetSequence(big.NewInt(10)) c.current.SetRound(big.NewInt(10)) } } return sys }(), makeBlock(1), istanbulcommon.ErrOldMessage, false, }, } OUTER: for _, test := range testCases { test.system.Run(false) v0 := test.system.backends[0] r0 := v0.engine curView := r0.currentView() preprepare := &istanbul.Preprepare{ View: curView, Proposal: test.expectedRequest, } for i, v := range test.system.backends { // i == 0 is primary backend, it is responsible for send PRE-PREPARE messages to others. if i == 0 { continue } c := v.engine m, _ := ibfttypes.Encode(preprepare) _, val := r0.valSet.GetByAddress(v0.Address()) // run each backends and verify handlePreprepare function. if err := c.handlePreprepare(&ibfttypes.Message{ Code: ibfttypes.MsgPreprepare, Msg: m, Address: v0.Address(), }, val); err != nil { if err != test.expectedErr { t.Errorf("error mismatch: have %v, want %v", err, test.expectedErr) } continue OUTER } if c.state != ibfttypes.StatePreprepared { t.Errorf("state mismatch: have %v, want %v", c.state, ibfttypes.StatePreprepared) } if !test.existingBlock && !reflect.DeepEqual(c.current.Subject().View, curView) { t.Errorf("view mismatch: have %v, want %v", c.current.Subject().View, curView) } // verify prepare messages decodedMsg := new(ibfttypes.Message) err := decodedMsg.FromPayload(v.sentMsgs[0], nil) if err != nil { t.Errorf("error mismatch: have %v, want nil", err) } expectedCode := ibfttypes.MsgPrepare if test.existingBlock { expectedCode = ibfttypes.MsgCommit } if decodedMsg.Code != expectedCode { t.Errorf("message code mismatch: have %v, want %v", decodedMsg.Code, expectedCode) } var subject *istanbul.Subject err = decodedMsg.Decode(&subject) if err != nil { t.Errorf("error mismatch: have %v, want nil", err) } if !test.existingBlock && !reflect.DeepEqual(subject, c.current.Subject()) { t.Errorf("subject mismatch: have %v, want %v", subject, c.current.Subject()) } } } } func TestHandlePreprepareWithLock(t *testing.T) { N := uint64(4) // replica 0 is the proposer, it will send messages to others F := uint64(1) // F does not affect tests proposal := newTestProposal() mismatchProposal := makeBlock(10) newSystem := func() *testSystem { sys := NewTestSystemWithBackend(N, F) for i, backend := range sys.backends { c := backend.engine c.valSet = backend.peers if i != 0 { c.state = ibfttypes.StateAcceptRequest } c.roundChangeSet = newRoundChangeSet(c.valSet) } return sys } testCases := []struct { system *testSystem proposal istanbul.Proposal lockProposal istanbul.Proposal }{ { newSystem(), proposal, proposal, }, { newSystem(), proposal, mismatchProposal, }, } for _, test := range testCases { test.system.Run(false) v0 := test.system.backends[0] r0 := v0.engine curView := r0.currentView() preprepare := &istanbul.Preprepare{ View: curView, Proposal: test.proposal, } lockPreprepare := &istanbul.Preprepare{ View: curView, Proposal: test.lockProposal, } for i, v := range test.system.backends { // i == 0 is primary backend, it is responsible for send PRE-PREPARE messages to others. if i == 0 { continue } c := v.engine c.current.SetPreprepare(lockPreprepare) c.current.LockHash() m, _ := ibfttypes.Encode(preprepare) _, val := r0.valSet.GetByAddress(v0.Address()) if err := c.handlePreprepare(&ibfttypes.Message{ Code: ibfttypes.MsgPreprepare, Msg: m, Address: v0.Address(), }, val); err != nil { t.Errorf("error mismatch: have %v, want nil", err) } if test.proposal == test.lockProposal { if c.state != ibfttypes.StatePrepared { t.Errorf("state mismatch: have %v, want %v", c.state, ibfttypes.StatePreprepared) } if !reflect.DeepEqual(curView, c.currentView()) { t.Errorf("view mismatch: have %v, want %v", c.currentView(), curView) } } else { // Should stay at ibfttypes.StateAcceptRequest if c.state != ibfttypes.StateAcceptRequest { t.Errorf("state mismatch: have %v, want %v", c.state, ibfttypes.StateAcceptRequest) } // Should have triggered a round change expectedView := &istanbul.View{ Sequence: curView.Sequence, Round: big.NewInt(1), } if !reflect.DeepEqual(expectedView, c.currentView()) { t.Errorf("view mismatch: have %v, want %v", c.currentView(), expectedView) } } } } }