Conversation
67855a5 to
86777fd
Compare
blockchain/store_test.go
Outdated
| part2 = partSet.GetPart(1) | ||
| seenCommit1 = &types.Commit{Precommits: []*types.Vote{{Height: 10, | ||
| Timestamp: time.Now().UTC()}}} | ||
| Timestamp: types.Now()}}} |
There was a problem hiding this comment.
Maybe it will be better to call this types.TimeNow() or create a time subpackage, so this will be tmtime.Now()
| PeerQueryMaj23SleepDuration int `mapstructure:"peer_query_maj23_sleep_duration"` | ||
|
|
||
| // Block time parameters in milliseconds | ||
| BlockTimeIota int `mapstructure:"blocktime_iota"` |
config/config.go
Outdated
| return cfg | ||
| } | ||
|
|
||
| // MinValidVoteTime returns the minimum acceptable block time. See the BFT time spec. |
consensus/state.go
Outdated
| func (cs *ConsensusState) signVote(type_ byte, hash []byte, header types.PartSetHeader) (*types.Vote, error) { | ||
| addr := cs.privValidator.GetAddress() | ||
| valIndex, _ := cs.Validators.GetByAddress(addr) | ||
| timestamp := cs.voteTime() |
There was a problem hiding this comment.
why create additional variable?
There was a problem hiding this comment.
You are right, it can be inlined.
|
|
||
| data := nBytes(n) | ||
| enc.Encode(&TimedWALMessage{Msg: data, Time: time.Now().Round(time.Second)}) | ||
| enc.Encode(&TimedWALMessage{Msg: data, Time: time.Now().Round(time.Second).UTC()}) |
There was a problem hiding this comment.
why not use tmtypes.Now() here?
There was a problem hiding this comment.
The difference seems to be .Round(time.Second) where in types.Now() we do Round(0)
There was a problem hiding this comment.
Is there some reason we need to round to the nearest second? Or let's just use tmtypes.Now ?
| return block, block.MakePartSet(state.ConsensusParams.BlockGossip.BlockPartSizeBytes) | ||
| } | ||
|
|
||
| func MedianTime(commit *types.Commit, validators *types.ValidatorSet) time.Time { |
ebuchman
left a comment
There was a problem hiding this comment.
Great start, thanks Zarko!
Only other thing is it seems the changes could have been better separated into distinct commits - ie. one to switch over to types.Now(), another for the BFT time stuff. Not a big deal though.
| now := types.Now() | ||
| minVoteTime := now | ||
| // TODO: We should remove next line in case we don't vote for v in case cs.ProposalBlock == nil, | ||
| // even if cs.LockedBlock != nil. See https://github.com/tendermint/spec. |
There was a problem hiding this comment.
In the spec we don't vote for lockedBlock in case we don't agree with Proposal. In case we decide to align with the spec (which I would suggest as Prevoting always for lockedBlock is optimisation which is not clear how much it brings), then the logic here can be simplified as it will only depend on ProposalBlock.
docs/spec/consensus/bft-time.md
Outdated
| the voting power is not uniform (one process one vote), a vote message is actually an aggregator of the same votes whose | ||
| number is equal to the voting power of the process that has casted the corresponding votes message. | ||
| number is equal to the voting power of the process that has casted the corresponding votes message. | ||
| When computing weighted median, only votes with to `vote.BlockID = block.LastCommit.BlockID` are taken into account. |
state/state.go
Outdated
|
|
||
| // Set time | ||
| if height == 1 { | ||
| block.Time = types.Now() |
There was a problem hiding this comment.
should we consider genesis time in case its in the future ?
state/state.go
Outdated
| for _, vote := range commit.Precommits { | ||
| if vote != nil && vote.BlockID.Equals(commit.BlockID) { | ||
| _, validator := validators.GetByIndex(vote.ValidatorIndex) | ||
| timeToVotingPower[vote.Timestamp] = validator.VotingPower |
There was a problem hiding this comment.
Should this be += ? What if some validators have identical timestamps
There was a problem hiding this comment.
I guess this was resolved by moving to use WeightedTime struct instead?
types/time.go
Outdated
| "sort" | ||
| "time" | ||
| ) | ||
|
|
There was a problem hiding this comment.
We should add proper Godoc comments to each exposed function
types/time.go
Outdated
| // To store the keys in slice in sorted order | ||
| var keys []time.Time | ||
| for k := range timeToVotingPower { | ||
| keys = append(keys, k) |
There was a problem hiding this comment.
so maybe it would actually be better to have WeightedMedian(weightedTimes []WeightedTime) where
type WeightedTime struct {
Time time.Time
Weight int64
}
Then we only have to build and sort the one array, instead of building up the map first and then copying into an array
types/time_test.go
Outdated
| m[t5] = 10 // faulty processes | ||
|
|
||
| median = WeightedMedian(m) | ||
| assert.Equal(t, t3, median) // median always returns value between values of correct processes |
There was a problem hiding this comment.
can we include the assertion associated with this comment? it's not clear until reading this what the significance of faulty process is here.
Also, this seems like its a good candidate for table driven tests.
3889a2f to
f927c67
Compare
|
Tests are failing with: |
|
I have seen it. Working on it. |
d275f54 to
c44d354
Compare
c44d354 to
2770689
Compare
Codecov Report
@@ Coverage Diff @@
## develop #2203 +/- ##
===========================================
- Coverage 61% 60.98% -0.02%
===========================================
Files 196 197 +1
Lines 16173 16238 +65
===========================================
+ Hits 9866 9903 +37
- Misses 5451 5478 +27
- Partials 856 857 +1
|
| "testing" | ||
| "time" | ||
|
|
||
| amino "github.com/tendermint/go-amino" |
There was a problem hiding this comment.
please don't remove this alias
There was a problem hiding this comment.
IDE removed it. Goland is great but not very flexible when it comes to configuration.
|
|
||
| // Write the wal message | ||
| if err := wal.enc.Encode(&TimedWALMessage{time.Now(), msg}); err != nil { | ||
| if err := wal.enc.Encode(&TimedWALMessage{tmtime.Now(), msg}); err != nil { |
There was a problem hiding this comment.
Do you think using time without monotonic part is beneficial for WAL file?
There was a problem hiding this comment.
I am not sure to understand what do you mean? I guess idea with using tmtime.Now everywhere is being consistent about time.
There was a problem hiding this comment.
I will try to explain. By stripping the monotonic part (that's what you're doing by calling tine.Now().Round(0)), if you try to compare two instances (t1 >= t2), Golang will only use wall clock (can be easily incorrect). https://golang.org/pkg/time/#hdr-Monotonic_Clocks
It looks like we're using time here for debugging, so that's fine. Please ignore
There was a problem hiding this comment.
Thanks for explaining this. Could we have a problem here:
Line 1673 in 412ba20
There was a problem hiding this comment.
If locked/proposed block come from the outside (other nodes), then it does not matter cause "monotonic clock" is local-only property!
state/execution_test.go
Outdated
| } | ||
| } | ||
|
|
||
| func (state State) createBlock( |
There was a problem hiding this comment.
why not use state.MakeBlock?
There was a problem hiding this comment.
state.MakeBlock expects state.LastValidators not to be empty because of the way how BFT time is set. This is the point discussed on Slack. But this test forces empty validator set (I don't really understand what is being tested).
for heights >= 2
| CreateEmptyBlocksInterval: 0, | ||
| PeerGossipSleepDuration: 100, | ||
| PeerQueryMaj23SleepDuration: 2000, | ||
| BlockTimeIota: 1000, |
There was a problem hiding this comment.
I think this needs to be a consensus parameter since it determines validity for blocks
|
|
||
| data := nBytes(n) | ||
| enc.Encode(&TimedWALMessage{Msg: data, Time: time.Now().Round(time.Second)}) | ||
| enc.Encode(&TimedWALMessage{Msg: data, Time: time.Now().Round(time.Second).UTC()}) |
There was a problem hiding this comment.
Is there some reason we need to round to the nearest second? Or let's just use tmtypes.Now ?
| } | ||
| } | ||
|
|
||
| return tmtime.WeightedMedian(weightedTimes, totalVotingPower) |
There was a problem hiding this comment.
can we just call vals.TotalVotingPower() instead of accumulating it ourselves here?
| if height == 1 { | ||
| block.Time = tmtime.Now() | ||
| if block.Time.Before(state.LastBlockTime) { | ||
| block.Time = state.LastBlockTime // state.LastBlockTime for height == 1 is genesis time |
There was a problem hiding this comment.
This suggests that #2294 isnt fixed here? Or at least it's still possible for a proposer to make a block before their local time reaches the genesis time. In any case I think we would want the first block to happen after the genesis time, and ala #2294, an honest proposer shouldn't make a block until they're locally at least that time.
There was a problem hiding this comment.
Good point. I guess that the right way to address it would be not to start consensus machinery before genesis time. "Waiting" for genesis time probably shouldn't interfere with this logic.
There was a problem hiding this comment.
Good point. I guess that the right way to address it would be not to start consensus machinery before genesis time.
this is also requested in #2294
| } | ||
|
|
||
| // Validate block Time | ||
| if block.Height > 1 { |
There was a problem hiding this comment.
should we add a clause Height==1 and require that it's at least the genesis time ? presumably, actually it should be some time after the genesis time ?
There was a problem hiding this comment.
Probably we can enforce only that it's at least the genesis time.
|
|
||
| // Now returns UTC time rounded since the zero time. | ||
| func Now() time.Time { | ||
| return time.Now().Round(0).UTC() |
There was a problem hiding this comment.
Can we clarify what we achieve with the Round(0) here ? It get's rid of the monotonic part according to https://golang.org/pkg/time/#Time.Round and https://golang.org/pkg/time/#hdr-Monotonic_Clocks but not 100% sure why we're doing that.
There was a problem hiding this comment.
@jaekwon Can you please provide some input here? I have imported this from your old BFT time PR.
There was a problem hiding this comment.
Stripping it is for time equality.
If we want to compare timestamps for equality, and you have time that was generated without Round(0) and compare it against one that was, they won't be equal. This might happen when you generate a block or vote (which generates a time locally, which without Round(0) would include the monotonic component), and then later you want to compare the timestamp there which was generated locally, against something that you encoded/decoded, using reflect.DeepEqual or assert/require.Equal.
We certainly don't need to use the monotonic part for most usages of time, because when we're setting the time of anything on the blockchain, we only care about what is encoded, nothing subjective. So, in order to make reflect.DeepEqual and other equality methods usable easily with Tendermint types, we should strip the monotonic component when creating them, like with this Now() method (for most usages of time anyways).
I remember that I was motivated to do this because a test was failing, I forget which.
| return time.Now().Round(0).UTC() | ||
| } | ||
|
|
||
| type WeightedTime struct { |
There was a problem hiding this comment.
Exported types need comments
state/state.go
Outdated
| for _, vote := range commit.Precommits { | ||
| if vote != nil && vote.BlockID.Equals(commit.BlockID) { | ||
| _, validator := validators.GetByIndex(vote.ValidatorIndex) | ||
| timeToVotingPower[vote.Timestamp] = validator.VotingPower |
There was a problem hiding this comment.
I guess this was resolved by moving to use WeightedTime struct instead?
There was a problem hiding this comment.
I think we can merge this, but will make a new issue for the follow up thanks @milosevic !
| "github.com/stretchr/testify/assert" | ||
| ) | ||
|
|
||
| func TestWeightedMedian(t *testing.T) { |
There was a problem hiding this comment.
should use table driven tests

Fix #2013.