mirror of
https://github.com/fluencelabs/tendermint
synced 2025-06-24 10:11:48 +00:00
make byzantine logic testable
This commit is contained in:
@ -1,6 +1,7 @@
|
||||
package consensus
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"testing"
|
||||
@ -8,7 +9,10 @@ import (
|
||||
|
||||
"github.com/tendermint/tendermint/config/tendermint_test"
|
||||
|
||||
"github.com/tendermint/ed25519"
|
||||
. "github.com/tendermint/go-common"
|
||||
cfg "github.com/tendermint/go-config"
|
||||
"github.com/tendermint/go-crypto"
|
||||
dbm "github.com/tendermint/go-db"
|
||||
"github.com/tendermint/go-events"
|
||||
"github.com/tendermint/go-p2p"
|
||||
@ -31,7 +35,7 @@ func resetConfigTimeouts() {
|
||||
// config.Set("timeout_commit", 1000)
|
||||
}
|
||||
|
||||
func TestReactor(t *testing.T) {
|
||||
func _TestReactor(t *testing.T) {
|
||||
resetConfigTimeouts()
|
||||
N := 4
|
||||
css := randConsensusNet(N)
|
||||
@ -80,3 +84,201 @@ func TestReactor(t *testing.T) {
|
||||
t.Fatalf("Timed out waiting for all validators to commit first block")
|
||||
}
|
||||
}
|
||||
|
||||
func _TestByzantine(t *testing.T) {
|
||||
resetConfigTimeouts()
|
||||
N := 4
|
||||
css := randConsensusNet(N)
|
||||
|
||||
switches := make([]*p2p.Switch, N)
|
||||
for i := 0; i < N; i++ {
|
||||
switches[i] = p2p.NewSwitch(cfg.NewMapConfig(nil))
|
||||
}
|
||||
|
||||
reactors := make([]*ConsensusReactor, N)
|
||||
eventChans := make([]chan interface{}, N)
|
||||
for i := 0; i < N; i++ {
|
||||
blockStoreDB := dbm.NewDB(Fmt("blockstore%d", i), config.GetString("db_backend"), config.GetString("db_dir"))
|
||||
blockStore := bc.NewBlockStore(blockStoreDB)
|
||||
|
||||
if i == 0 {
|
||||
// make byzantine
|
||||
css[i].decideProposal = func(j int) func(int, int) {
|
||||
return func(height, round int) {
|
||||
fmt.Println("hmph", j)
|
||||
byzantineDecideProposalFunc(height, round, css[j], switches[j])
|
||||
}
|
||||
}(i)
|
||||
css[i].doPrevote = func(height, round int) {}
|
||||
css[i].setProposal = func(j int) func(proposal *types.Proposal) error {
|
||||
return func(proposal *types.Proposal) error {
|
||||
return byzantineSetProposal(proposal, css[j], switches[j])
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
reactors[i] = NewConsensusReactor(css[i], blockStore, false)
|
||||
reactors[i].SetPrivValidator(css[i].privValidator)
|
||||
|
||||
eventSwitch := events.NewEventSwitch()
|
||||
_, err := eventSwitch.Start()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to start switch: %v", err)
|
||||
}
|
||||
|
||||
reactors[i].SetEventSwitch(eventSwitch)
|
||||
eventChans[i] = subscribeToEvent(eventSwitch, "tester", types.EventStringNewBlock(), 1)
|
||||
}
|
||||
p2p.MakeConnectedSwitches(N, func(i int, s *p2p.Switch) *p2p.Switch {
|
||||
s.AddReactor("CONSENSUS", reactors[i])
|
||||
return s
|
||||
}, net.Pipe)
|
||||
|
||||
// wait till everyone makes the first new block
|
||||
wg := new(sync.WaitGroup)
|
||||
wg.Add(N)
|
||||
for i := 0; i < N; i++ {
|
||||
go func(j int) {
|
||||
<-eventChans[j]
|
||||
wg.Done()
|
||||
}(i)
|
||||
}
|
||||
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(done)
|
||||
}()
|
||||
|
||||
tick := time.NewTicker(time.Second * 3)
|
||||
select {
|
||||
case <-done:
|
||||
case <-tick.C:
|
||||
t.Fatalf("Timed out waiting for all validators to commit first block")
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------
|
||||
// byzantine consensus functions
|
||||
|
||||
func byzantineDecideProposalFunc(height, round int, cs *ConsensusState, sw *p2p.Switch) {
|
||||
// byzantine user should create two proposals and try to split the vote.
|
||||
// Avoid sending on internalMsgQueue and running consensus state.
|
||||
|
||||
// Create a new proposal block from state/txs from the mempool.
|
||||
block1, blockParts1 := cs.createProposalBlock()
|
||||
polRound, polBlockID := cs.Votes.POLInfo()
|
||||
proposal1 := types.NewProposal(height, round, blockParts1.Header(), polRound, polBlockID)
|
||||
cs.privValidator.SignProposal(cs.state.ChainID, proposal1) // byzantine doesnt err
|
||||
|
||||
// Create a new proposal block from state/txs from the mempool.
|
||||
block2, blockParts2 := cs.createProposalBlock()
|
||||
polRound, polBlockID = cs.Votes.POLInfo()
|
||||
proposal2 := types.NewProposal(height, round, blockParts2.Header(), polRound, polBlockID)
|
||||
cs.privValidator.SignProposal(cs.state.ChainID, proposal2) // byzantine doesnt err
|
||||
|
||||
log.Notice("Byzantine: broadcasting conflicting proposals")
|
||||
// broadcast conflicting proposals/block parts to peers
|
||||
peers := sw.Peers().List()
|
||||
for i, peer := range peers {
|
||||
if i < len(peers)/2 {
|
||||
go sendProposalAndParts(height, round, cs, peer, proposal1, block1, blockParts1)
|
||||
} else {
|
||||
go sendProposalAndParts(height, round, cs, peer, proposal2, block2, blockParts2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func sendProposalAndParts(height, round int, cs *ConsensusState, peer *p2p.Peer, proposal *types.Proposal, block *types.Block, parts *types.PartSet) {
|
||||
// proposal
|
||||
msg := &ProposalMessage{Proposal: proposal}
|
||||
peer.Send(DataChannel, struct{ ConsensusMessage }{msg})
|
||||
|
||||
// parts
|
||||
for i := 0; i < parts.Total(); i++ {
|
||||
part := parts.GetPart(i)
|
||||
msg := &BlockPartMessage{
|
||||
Height: height, // This tells peer that this part applies to us.
|
||||
Round: round, // This tells peer that this part applies to us.
|
||||
Part: part,
|
||||
}
|
||||
peer.Send(DataChannel, struct{ ConsensusMessage }{msg})
|
||||
}
|
||||
|
||||
// votes
|
||||
prevote, _ := cs.signVote(types.VoteTypePrevote, block.Hash(), parts.Header())
|
||||
peer.Send(VoteChannel, struct{ ConsensusMessage }{&VoteMessage{prevote}})
|
||||
precommit, _ := cs.signVote(types.VoteTypePrecommit, block.Hash(), parts.Header())
|
||||
peer.Send(VoteChannel, struct{ ConsensusMessage }{&VoteMessage{precommit}})
|
||||
}
|
||||
|
||||
func byzantineSetProposal(proposal *types.Proposal, cs *ConsensusState, sw *p2p.Switch) error {
|
||||
peers := sw.Peers().List()
|
||||
for _, peer := range peers {
|
||||
// votes
|
||||
var blockHash []byte // XXX proposal.BlockHash
|
||||
blockHash = []byte{0, 1, 0, 2, 0, 3}
|
||||
prevote, _ := cs.signVote(types.VoteTypePrevote, blockHash, proposal.BlockPartsHeader)
|
||||
peer.Send(VoteChannel, struct{ ConsensusMessage }{&VoteMessage{prevote}})
|
||||
precommit, _ := cs.signVote(types.VoteTypePrecommit, blockHash, proposal.BlockPartsHeader)
|
||||
peer.Send(VoteChannel, struct{ ConsensusMessage }{&VoteMessage{precommit}})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//----------------------------------------
|
||||
// byzantine privValidator
|
||||
type ByzantinePrivValidator struct {
|
||||
Address []byte `json:"address"`
|
||||
PubKey crypto.PubKey `json:"pub_key"`
|
||||
|
||||
// PrivKey should be empty if a Signer other than the default is being used.
|
||||
PrivKey crypto.PrivKey `json:"priv_key"`
|
||||
types.Signer `json:"-"`
|
||||
|
||||
mtx sync.Mutex
|
||||
}
|
||||
|
||||
func (privVal *ByzantinePrivValidator) SetSigner(s types.Signer) {
|
||||
privVal.Signer = s
|
||||
}
|
||||
|
||||
// Generates a new validator with private key.
|
||||
func GenPrivValidator() *ByzantinePrivValidator {
|
||||
privKeyBytes := new([64]byte)
|
||||
copy(privKeyBytes[:32], crypto.CRandBytes(32))
|
||||
pubKeyBytes := ed25519.MakePublicKey(privKeyBytes)
|
||||
pubKey := crypto.PubKeyEd25519(*pubKeyBytes)
|
||||
privKey := crypto.PrivKeyEd25519(*privKeyBytes)
|
||||
return &ByzantinePrivValidator{
|
||||
Address: pubKey.Address(),
|
||||
PubKey: pubKey,
|
||||
PrivKey: privKey,
|
||||
Signer: types.NewDefaultSigner(privKey),
|
||||
}
|
||||
}
|
||||
|
||||
func (privVal *ByzantinePrivValidator) GetAddress() []byte {
|
||||
return privVal.Address
|
||||
}
|
||||
|
||||
func (privVal *ByzantinePrivValidator) SignVote(chainID string, vote *types.Vote) error {
|
||||
privVal.mtx.Lock()
|
||||
defer privVal.mtx.Unlock()
|
||||
|
||||
// Sign
|
||||
vote.Signature = privVal.Sign(types.SignBytes(chainID, vote)).(crypto.SignatureEd25519)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (privVal *ByzantinePrivValidator) SignProposal(chainID string, proposal *types.Proposal) error {
|
||||
privVal.mtx.Lock()
|
||||
defer privVal.mtx.Unlock()
|
||||
|
||||
// Sign
|
||||
proposal.Signature = privVal.Sign(types.SignBytes(chainID, proposal)).(crypto.SignatureEd25519)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (privVal *ByzantinePrivValidator) String() string {
|
||||
return Fmt("PrivValidator{%X}", privVal.Address)
|
||||
}
|
||||
|
Reference in New Issue
Block a user