Tendermint consensus state test
September 01, 2019
Tendermint consensus state plays a key role in maintaining consensus state of all validators across tendermint network. Tendermint source code includes consensus state test suites. I tried some of them and found one test case seemingly uncertain. It is TestStateLockPOLRelock in tenderminit/consensus/state_test.go
.
Test senario of this test case is
Round1 (cs1, B) // B B B B// B nil B nil
Round2 (vs2, C) // B C C C // C C C _)
If we run this test case, we would get something like this:
PASS
ok github.com/tendermint/tendermint/consensus 0.062s
This is expected. If we run this test case with log option on, we would see something like:
...
I[2019-07-18|14:20:28.283] Updating ValidBlock because of POL. module=consensus validRound=0 POLRound=1
I[2019-07-18|14:20:28.283] enterPrecommit(1/1). Current: 1/1/RoundStepPrevote module=consensus height=1 round=1
I[2019-07-18|14:20:28.283] enterPrecommit: +2/3 prevoted locked block. Relocking module=consensus height=1 round=1
...
in the middle of log output. We can see Relocking
at second round(height = 1, round = 1) of enterPrecommit step. This log message seems to match well with this test case name TestStateLockPOLRelock
.
In the mean time, if we modified the test code of line 571 from original:
// now lets add prevotes from everyone else for the new block
signAddVotes(cs1, types.PrevoteType, propBlockHash, propBlockParts.Header(), vs2, vs3, vs4)
to like this:
// now lets add prevotes from everyone else for the new block
signAddVotes(cs1, types.PrevoteType, propBlockHash, propBlockParts.Header(), vs3, vs4)
and run the test case, it would just pass through OK . The test should have failed because we get prevotes on new proposal from only vs3, vs4, which does not compose majority voting.
This indicates that either the consensus state code or the test code has bug. If we assumed that the proposal block B in the first round (happens to) be equal to the proposal block C in the second round, then the modified test code would pass OK. But this assumption is not true in general.
After having made somehow that proposal block B is not equal to the proposal block C, running the modified test case has indeed failed and log output was something like:
--- FAIL: TestStateLockPOLRelock (0.03s)
state_test.go:554: ### ONTO ROUND 1
panic: Expected precommit to be for proposal block [recovered]
panic: Expected precommit to be for proposal block
This was exactly what I expected!
Runnung the original test code with the proposal B not equal to the proposal C produced log output something like:
...
I[2019-07-18|14:25:10.159] Unlocking because of POL. module=consensus lockedRound=0 POLRound=1
I[2019-07-18|14:25:10.159] Updating ValidBlock because of POL. module=consensus validRound=0 POLRound=1
I[2019-07-18|14:25:10.159] enterPrecommit(1/1). Current: 1/1/RoundStepPrevoteWait module=consensus height=1 round=1
I[2019-07-18|14:25:10.159] enterPrecommit: +2/3 prevoted proposal block. Locking module=consensus height=1 round=1 hash=66FF782F43A417A01FFD365FD0EC3CB8F9B9B2070725A68AC8EC6E766867CE3D
...
This indicates that the consensus state under test completely unlocks proposal block B locked at the first round and then locks new proposal block C at the second round.
On the other hand, relocking from the related source code shows that it is a way to lock the same block again
at new round, updating only LockedRound
field of the state with new round. Block to be locked remains the same as the previously locked block.
In this sense, the name of this test case TestStateLockPOLRelock
is confusing and can mislead tendermint code viewer like myself.
Written by Sangche. Github