|
@ -3,32 +3,190 @@ package consensus |
|
|
import ( |
|
|
import ( |
|
|
"context" |
|
|
"context" |
|
|
"fmt" |
|
|
"fmt" |
|
|
|
|
|
"os" |
|
|
|
|
|
"path" |
|
|
"sync" |
|
|
"sync" |
|
|
"testing" |
|
|
"testing" |
|
|
"time" |
|
|
"time" |
|
|
|
|
|
|
|
|
|
|
|
"github.com/stretchr/testify/assert" |
|
|
"github.com/stretchr/testify/require" |
|
|
"github.com/stretchr/testify/require" |
|
|
|
|
|
|
|
|
|
|
|
dbm "github.com/tendermint/tm-db" |
|
|
|
|
|
|
|
|
|
|
|
abcicli "github.com/tendermint/tendermint/abci/client" |
|
|
|
|
|
abci "github.com/tendermint/tendermint/abci/types" |
|
|
|
|
|
"github.com/tendermint/tendermint/evidence" |
|
|
|
|
|
"github.com/tendermint/tendermint/libs/log" |
|
|
"github.com/tendermint/tendermint/libs/service" |
|
|
"github.com/tendermint/tendermint/libs/service" |
|
|
|
|
|
tmsync "github.com/tendermint/tendermint/libs/sync" |
|
|
|
|
|
mempl "github.com/tendermint/tendermint/mempool" |
|
|
"github.com/tendermint/tendermint/p2p" |
|
|
"github.com/tendermint/tendermint/p2p" |
|
|
tmproto "github.com/tendermint/tendermint/proto/tendermint/types" |
|
|
tmproto "github.com/tendermint/tendermint/proto/tendermint/types" |
|
|
sm "github.com/tendermint/tendermint/state" |
|
|
sm "github.com/tendermint/tendermint/state" |
|
|
|
|
|
"github.com/tendermint/tendermint/store" |
|
|
"github.com/tendermint/tendermint/types" |
|
|
"github.com/tendermint/tendermint/types" |
|
|
) |
|
|
) |
|
|
|
|
|
|
|
|
//----------------------------------------------
|
|
|
//----------------------------------------------
|
|
|
// byzantine failures
|
|
|
// byzantine failures
|
|
|
|
|
|
|
|
|
|
|
|
// Byzantine node sends two different prevotes (nil and blockID) to the same validator
|
|
|
|
|
|
func TestByzantinePrevoteEquivocation(t *testing.T) { |
|
|
|
|
|
const nValidators = 4 |
|
|
|
|
|
const byzantineNode = 0 |
|
|
|
|
|
testName := "consensus_byzantine_test" |
|
|
|
|
|
tickerFunc := newMockTickerFunc(true) |
|
|
|
|
|
appFunc := newCounter |
|
|
|
|
|
|
|
|
|
|
|
genDoc, privVals := randGenesisDoc(nValidators, false, 30) |
|
|
|
|
|
css := make([]*State, nValidators) |
|
|
|
|
|
|
|
|
|
|
|
for i := 0; i < nValidators; i++ { |
|
|
|
|
|
logger := consensusLogger().With("test", "byzantine", "validator", i) |
|
|
|
|
|
stateDB := dbm.NewMemDB() // each state needs its own db
|
|
|
|
|
|
state, _ := sm.LoadStateFromDBOrGenesisDoc(stateDB, genDoc) |
|
|
|
|
|
thisConfig := ResetConfig(fmt.Sprintf("%s_%d", testName, i)) |
|
|
|
|
|
defer os.RemoveAll(thisConfig.RootDir) |
|
|
|
|
|
ensureDir(path.Dir(thisConfig.Consensus.WalFile()), 0700) // dir for wal
|
|
|
|
|
|
app := appFunc() |
|
|
|
|
|
vals := types.TM2PB.ValidatorUpdates(state.Validators) |
|
|
|
|
|
app.InitChain(abci.RequestInitChain{Validators: vals}) |
|
|
|
|
|
|
|
|
|
|
|
blockDB := dbm.NewMemDB() |
|
|
|
|
|
blockStore := store.NewBlockStore(blockDB) |
|
|
|
|
|
|
|
|
|
|
|
// one for mempool, one for consensus
|
|
|
|
|
|
mtx := new(tmsync.Mutex) |
|
|
|
|
|
proxyAppConnMem := abcicli.NewLocalClient(mtx, app) |
|
|
|
|
|
proxyAppConnCon := abcicli.NewLocalClient(mtx, app) |
|
|
|
|
|
|
|
|
|
|
|
// Make Mempool
|
|
|
|
|
|
mempool := mempl.NewCListMempool(thisConfig.Mempool, proxyAppConnMem, 0) |
|
|
|
|
|
mempool.SetLogger(log.TestingLogger().With("module", "mempool")) |
|
|
|
|
|
if thisConfig.Consensus.WaitForTxs() { |
|
|
|
|
|
mempool.EnableTxsAvailable() |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Make a full instance of the evidence pool
|
|
|
|
|
|
evidenceDB := dbm.NewMemDB() |
|
|
|
|
|
evpool, err := evidence.NewPool(stateDB, evidenceDB, blockStore) |
|
|
|
|
|
require.NoError(t, err) |
|
|
|
|
|
evpool.SetLogger(logger.With("module", "evidence")) |
|
|
|
|
|
|
|
|
|
|
|
// Make State
|
|
|
|
|
|
blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), proxyAppConnCon, mempool, evpool) |
|
|
|
|
|
cs := NewState(thisConfig.Consensus, state, blockExec, blockStore, mempool, evpool) |
|
|
|
|
|
cs.SetLogger(cs.Logger) |
|
|
|
|
|
// set private validator
|
|
|
|
|
|
pv := privVals[i] |
|
|
|
|
|
cs.SetPrivValidator(pv) |
|
|
|
|
|
|
|
|
|
|
|
eventBus := types.NewEventBus() |
|
|
|
|
|
eventBus.SetLogger(log.TestingLogger().With("module", "events")) |
|
|
|
|
|
eventBus.Start() |
|
|
|
|
|
cs.SetEventBus(eventBus) |
|
|
|
|
|
|
|
|
|
|
|
cs.SetTimeoutTicker(tickerFunc()) |
|
|
|
|
|
cs.SetLogger(logger) |
|
|
|
|
|
|
|
|
|
|
|
css[i] = cs |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// initialize the reactors for each of the validators
|
|
|
|
|
|
reactors := make([]*Reactor, nValidators) |
|
|
|
|
|
blocksSubs := make([]types.Subscription, 0) |
|
|
|
|
|
eventBuses := make([]*types.EventBus, nValidators) |
|
|
|
|
|
for i := 0; i < nValidators; i++ { |
|
|
|
|
|
reactors[i] = NewReactor(css[i], true) // so we dont start the consensus states
|
|
|
|
|
|
reactors[i].SetLogger(css[i].Logger) |
|
|
|
|
|
|
|
|
|
|
|
// eventBus is already started with the cs
|
|
|
|
|
|
eventBuses[i] = css[i].eventBus |
|
|
|
|
|
reactors[i].SetEventBus(eventBuses[i]) |
|
|
|
|
|
|
|
|
|
|
|
blocksSub, err := eventBuses[i].Subscribe(context.Background(), testSubscriber, types.EventQueryNewBlock) |
|
|
|
|
|
require.NoError(t, err) |
|
|
|
|
|
blocksSubs = append(blocksSubs, blocksSub) |
|
|
|
|
|
|
|
|
|
|
|
if css[i].state.LastBlockHeight == 0 { //simulate handle initChain in handshake
|
|
|
|
|
|
sm.SaveState(css[i].blockExec.DB(), css[i].state) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
// make connected switches and start all reactors
|
|
|
|
|
|
p2p.MakeConnectedSwitches(config.P2P, nValidators, func(i int, s *p2p.Switch) *p2p.Switch { |
|
|
|
|
|
s.AddReactor("CONSENSUS", reactors[i]) |
|
|
|
|
|
s.SetLogger(reactors[i].conS.Logger.With("module", "p2p")) |
|
|
|
|
|
return s |
|
|
|
|
|
}, p2p.Connect2Switches) |
|
|
|
|
|
|
|
|
|
|
|
// create byzantine validator
|
|
|
|
|
|
bcs := css[byzantineNode] |
|
|
|
|
|
|
|
|
|
|
|
// alter prevote so that the byzantine node double votes when height is 2
|
|
|
|
|
|
bcs.doPrevote = func(height int64, round int32) { |
|
|
|
|
|
// allow first height to happen normally so that byzantine validator is no longer proposer
|
|
|
|
|
|
if height == 2 { |
|
|
|
|
|
bcs.Logger.Info("Sending two votes") |
|
|
|
|
|
prevote1, err := bcs.signVote(tmproto.PrevoteType, bcs.ProposalBlock.Hash(), bcs.ProposalBlockParts.Header()) |
|
|
|
|
|
require.NoError(t, err) |
|
|
|
|
|
prevote2, err := bcs.signVote(tmproto.PrevoteType, nil, types.PartSetHeader{}) |
|
|
|
|
|
require.NoError(t, err) |
|
|
|
|
|
peerList := reactors[byzantineNode].Switch.Peers().List() |
|
|
|
|
|
bcs.Logger.Info("Getting peer list", "peers", peerList) |
|
|
|
|
|
// send two votes to all peers (1st to one half, 2nd to another half)
|
|
|
|
|
|
for i, peer := range peerList { |
|
|
|
|
|
if i < len(peerList)/2 { |
|
|
|
|
|
bcs.Logger.Info("Signed and pushed vote", "vote", prevote1, "peer", peer) |
|
|
|
|
|
peer.Send(VoteChannel, MustEncode(&VoteMessage{prevote1})) |
|
|
|
|
|
} else { |
|
|
|
|
|
bcs.Logger.Info("Signed and pushed vote", "vote", prevote2, "peer", peer) |
|
|
|
|
|
peer.Send(VoteChannel, MustEncode(&VoteMessage{prevote2})) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} else { |
|
|
|
|
|
bcs.Logger.Info("Behaving normally") |
|
|
|
|
|
bcs.defaultDoPrevote(height, round) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// start the consensus reactors
|
|
|
|
|
|
for i := 0; i < nValidators; i++ { |
|
|
|
|
|
s := reactors[i].conS.GetState() |
|
|
|
|
|
reactors[i].SwitchToConsensus(s, false) |
|
|
|
|
|
} |
|
|
|
|
|
defer stopConsensusNet(log.TestingLogger(), reactors, eventBuses) |
|
|
|
|
|
|
|
|
|
|
|
// Check that evidence is submitted and committed at the third height
|
|
|
|
|
|
for i := 0; i < 2; i++ { |
|
|
|
|
|
timeoutWaitGroup(t, nValidators, func(j int) { |
|
|
|
|
|
<-blocksSubs[j].Out() |
|
|
|
|
|
}, css) |
|
|
|
|
|
} |
|
|
|
|
|
timeoutWaitGroup(t, nValidators, func(j int) { |
|
|
|
|
|
msg := <-blocksSubs[j].Out() |
|
|
|
|
|
block := msg.Data().(types.EventDataNewBlock).Block |
|
|
|
|
|
// assert that we have evidence
|
|
|
|
|
|
assert.True(t, len(block.Evidence.Evidence) == 1) |
|
|
|
|
|
// and that the evidence is of type DuplicateVoteEvidence
|
|
|
|
|
|
ev, ok := block.Evidence.Evidence[0].(*types.DuplicateVoteEvidence) |
|
|
|
|
|
assert.True(t, ok) |
|
|
|
|
|
// and that the address matches to that of the byzantine node
|
|
|
|
|
|
pubkey, _ := bcs.privValidator.GetPubKey() |
|
|
|
|
|
assert.Equal(t, []byte(pubkey.Address()), ev.Address()) |
|
|
|
|
|
}, css) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// 4 validators. 1 is byzantine. The other three are partitioned into A (1 val) and B (2 vals).
|
|
|
// 4 validators. 1 is byzantine. The other three are partitioned into A (1 val) and B (2 vals).
|
|
|
// byzantine validator sends conflicting proposals into A and B,
|
|
|
// byzantine validator sends conflicting proposals into A and B,
|
|
|
// and prevotes/precommits on both of them.
|
|
|
// and prevotes/precommits on both of them.
|
|
|
// B sees a commit, A doesn't.
|
|
|
// B sees a commit, A doesn't.
|
|
|
// Byzantine validator refuses to prevote.
|
|
|
|
|
|
// Heal partition and ensure A sees the commit
|
|
|
// Heal partition and ensure A sees the commit
|
|
|
func TestByzantine(t *testing.T) { |
|
|
|
|
|
|
|
|
func TestByzantineConflictingProposalsWithPartition(t *testing.T) { |
|
|
N := 4 |
|
|
N := 4 |
|
|
logger := consensusLogger().With("test", "byzantine") |
|
|
logger := consensusLogger().With("test", "byzantine") |
|
|
css, cleanup := randConsensusNet(N, "consensus_byzantine_test", newMockTickerFunc(false), newCounter) |
|
|
|
|
|
|
|
|
app := newCounter |
|
|
|
|
|
css, cleanup := randConsensusNet(N, "consensus_byzantine_test", newMockTickerFunc(false), app) |
|
|
defer cleanup() |
|
|
defer cleanup() |
|
|
|
|
|
|
|
|
// give the byzantine validator a normal ticker
|
|
|
// give the byzantine validator a normal ticker
|
|
@ -52,6 +210,9 @@ func TestByzantine(t *testing.T) { |
|
|
blocksSubs := make([]types.Subscription, N) |
|
|
blocksSubs := make([]types.Subscription, N) |
|
|
reactors := make([]p2p.Reactor, N) |
|
|
reactors := make([]p2p.Reactor, N) |
|
|
for i := 0; i < N; i++ { |
|
|
for i := 0; i < N; i++ { |
|
|
|
|
|
|
|
|
|
|
|
// enable txs so we can create different proposals
|
|
|
|
|
|
assertMempool(css[i].txNotifier).EnableTxsAvailable() |
|
|
// make first val byzantine
|
|
|
// make first val byzantine
|
|
|
if i == 0 { |
|
|
if i == 0 { |
|
|
// NOTE: Now, test validators are MockPV, which by default doesn't
|
|
|
// NOTE: Now, test validators are MockPV, which by default doesn't
|
|
@ -62,6 +223,8 @@ func TestByzantine(t *testing.T) { |
|
|
byzantineDecideProposalFunc(t, height, round, css[j], switches[j]) |
|
|
byzantineDecideProposalFunc(t, height, round, css[j], switches[j]) |
|
|
} |
|
|
} |
|
|
}(int32(i)) |
|
|
}(int32(i)) |
|
|
|
|
|
// We are setting the prevote function to do nothing because the prevoting
|
|
|
|
|
|
// and precommitting are done alongside the proposal.
|
|
|
css[i].doPrevote = func(height int64, round int32) {} |
|
|
css[i].doPrevote = func(height int64, round int32) {} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
@ -188,6 +351,9 @@ func byzantineDecideProposalFunc(t *testing.T, height int64, round int32, cs *St |
|
|
|
|
|
|
|
|
proposal1.Signature = p1.Signature |
|
|
proposal1.Signature = p1.Signature |
|
|
|
|
|
|
|
|
|
|
|
// some new transactions come in (this ensures that the proposals are different)
|
|
|
|
|
|
deliverTxsRange(cs, 0, 1) |
|
|
|
|
|
|
|
|
// Create a new proposal block from state/txs from the mempool.
|
|
|
// Create a new proposal block from state/txs from the mempool.
|
|
|
block2, blockParts2 := cs.createProposalBlock() |
|
|
block2, blockParts2 := cs.createProposalBlock() |
|
|
polRound, propBlockID = cs.ValidRound, types.BlockID{Hash: block2.Hash(), PartSetHeader: blockParts2.Header()} |
|
|
polRound, propBlockID = cs.ValidRound, types.BlockID{Hash: block2.Hash(), PartSetHeader: blockParts2.Header()} |
|
|