You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

501 lines
17 KiB

  1. package consensus
  2. import (
  3. "bytes"
  4. "context"
  5. "testing"
  6. "time"
  7. "github.com/stretchr/testify/assert"
  8. "github.com/stretchr/testify/require"
  9. "github.com/tendermint/tendermint/abci/example/kvstore"
  10. "github.com/tendermint/tendermint/internal/eventbus"
  11. tmpubsub "github.com/tendermint/tendermint/internal/pubsub"
  12. "github.com/tendermint/tendermint/internal/test/factory"
  13. "github.com/tendermint/tendermint/libs/log"
  14. tmtimemocks "github.com/tendermint/tendermint/libs/time/mocks"
  15. tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
  16. "github.com/tendermint/tendermint/types"
  17. )
  18. const (
  19. // blockTimeIota is used in the test harness as the time between
  20. // blocks when not otherwise specified.
  21. blockTimeIota = time.Millisecond
  22. )
  23. // pbtsTestHarness constructs a Tendermint network that can be used for testing the
  24. // implementation of the Proposer-Based timestamps algorithm.
  25. // It runs a series of consensus heights and captures timing of votes and events.
  26. type pbtsTestHarness struct {
  27. // configuration options set by the user of the test harness.
  28. pbtsTestConfiguration
  29. // The timestamp of the first block produced by the network.
  30. firstBlockTime time.Time
  31. // The Tendermint consensus state machine being run during
  32. // a run of the pbtsTestHarness.
  33. observedState *State
  34. // A stub for signing votes and messages using the key
  35. // from the observedState.
  36. observedValidator *validatorStub
  37. // A list of simulated validators that interact with the observedState and are
  38. // fully controlled by the test harness.
  39. otherValidators []*validatorStub
  40. // The mock time source used by all of the validator stubs in the test harness.
  41. // This mock clock allows the test harness to produce votes and blocks with arbitrary
  42. // timestamps.
  43. validatorClock *tmtimemocks.Source
  44. chainID string
  45. // channels for verifying that the observed validator completes certain actions.
  46. ensureProposalCh, roundCh, blockCh, ensureVoteCh <-chan tmpubsub.Message
  47. // channel of events from the observed validator annotated with the timestamp
  48. // the event was received.
  49. eventCh <-chan timestampedEvent
  50. currentHeight int64
  51. currentRound int32
  52. }
  53. type pbtsTestConfiguration struct {
  54. // The timestamp consensus parameters to be used by the state machine under test.
  55. synchronyParams types.SynchronyParams
  56. // The setting to use for the TimeoutPropose configuration parameter.
  57. timeoutPropose time.Duration
  58. // The genesis time
  59. genesisTime time.Time
  60. // The times offset from height 1 block time of the block proposed at height 2.
  61. height2ProposedBlockOffset time.Duration
  62. // The time offset from height 1 block time at which the proposal at height 2 should be delivered.
  63. height2ProposalTimeDeliveryOffset time.Duration
  64. // The time offset from height 1 block time of the block proposed at height 4.
  65. // At height 4, the proposed block and the deliver offsets are the same so
  66. // that timely-ness does not affect height 4.
  67. height4ProposedBlockOffset time.Duration
  68. }
  69. func newPBTSTestHarness(ctx context.Context, t *testing.T, tc pbtsTestConfiguration) pbtsTestHarness {
  70. t.Helper()
  71. const validators = 4
  72. cfg := configSetup(t)
  73. clock := new(tmtimemocks.Source)
  74. if tc.genesisTime.IsZero() {
  75. tc.genesisTime = time.Now()
  76. }
  77. if tc.height4ProposedBlockOffset == 0 {
  78. // Set a default height4ProposedBlockOffset.
  79. // Use a proposed block time that is greater than the time that the
  80. // block at height 2 was delivered. Height 3 is not relevant for testing
  81. // and always occurs blockTimeIota before height 4. If not otherwise specified,
  82. // height 4 therefore occurs 2*blockTimeIota after height 2.
  83. tc.height4ProposedBlockOffset = tc.height2ProposalTimeDeliveryOffset + 2*blockTimeIota
  84. }
  85. consensusParams := factory.ConsensusParams()
  86. consensusParams.Timeout.Propose = tc.timeoutPropose
  87. consensusParams.Synchrony = tc.synchronyParams
  88. state, privVals := makeGenesisState(ctx, t, cfg, genesisStateArgs{
  89. Params: consensusParams,
  90. Time: tc.genesisTime,
  91. Validators: validators,
  92. })
  93. cs := newState(ctx, t, log.NewNopLogger(), state, privVals[0], kvstore.NewApplication())
  94. vss := make([]*validatorStub, validators)
  95. for i := 0; i < validators; i++ {
  96. vss[i] = newValidatorStub(privVals[i], int32(i))
  97. }
  98. incrementHeight(vss[1:]...)
  99. for _, vs := range vss {
  100. vs.clock = clock
  101. }
  102. pubKey, err := vss[0].PrivValidator.GetPubKey(ctx)
  103. require.NoError(t, err)
  104. eventCh := timestampedCollector(ctx, t, cs.eventBus)
  105. return pbtsTestHarness{
  106. pbtsTestConfiguration: tc,
  107. observedValidator: vss[0],
  108. observedState: cs,
  109. otherValidators: vss[1:],
  110. validatorClock: clock,
  111. currentHeight: 1,
  112. chainID: cfg.ChainID(),
  113. roundCh: subscribe(ctx, t, cs.eventBus, types.EventQueryNewRound),
  114. ensureProposalCh: subscribe(ctx, t, cs.eventBus, types.EventQueryCompleteProposal),
  115. blockCh: subscribe(ctx, t, cs.eventBus, types.EventQueryNewBlock),
  116. ensureVoteCh: subscribeToVoterBuffered(ctx, t, cs, pubKey.Address()),
  117. eventCh: eventCh,
  118. }
  119. }
  120. func (p *pbtsTestHarness) observedValidatorProposerHeight(ctx context.Context, t *testing.T, previousBlockTime time.Time) (heightResult, time.Time) {
  121. p.validatorClock.On("Now").Return(p.genesisTime.Add(p.height2ProposedBlockOffset)).Times(6)
  122. ensureNewRound(t, p.roundCh, p.currentHeight, p.currentRound)
  123. timeout := time.Until(previousBlockTime.Add(ensureTimeout))
  124. ensureProposalWithTimeout(t, p.ensureProposalCh, p.currentHeight, p.currentRound, nil, timeout)
  125. rs := p.observedState.GetRoundState()
  126. bid := types.BlockID{Hash: rs.ProposalBlock.Hash(), PartSetHeader: rs.ProposalBlockParts.Header()}
  127. ensurePrevote(t, p.ensureVoteCh, p.currentHeight, p.currentRound)
  128. signAddVotes(ctx, t, p.observedState, tmproto.PrevoteType, p.chainID, bid, p.otherValidators...)
  129. signAddVotes(ctx, t, p.observedState, tmproto.PrecommitType, p.chainID, bid, p.otherValidators...)
  130. ensurePrecommit(t, p.ensureVoteCh, p.currentHeight, p.currentRound)
  131. ensureNewBlock(t, p.blockCh, p.currentHeight)
  132. vk, err := p.observedValidator.GetPubKey(ctx)
  133. require.NoError(t, err)
  134. res := collectHeightResults(ctx, t, p.eventCh, p.currentHeight, vk.Address())
  135. p.currentHeight++
  136. incrementHeight(p.otherValidators...)
  137. return res, rs.ProposalBlock.Time
  138. }
  139. func (p *pbtsTestHarness) height2(ctx context.Context, t *testing.T) heightResult {
  140. signer := p.otherValidators[0].PrivValidator
  141. return p.nextHeight(ctx, t, signer,
  142. p.firstBlockTime.Add(p.height2ProposalTimeDeliveryOffset),
  143. p.firstBlockTime.Add(p.height2ProposedBlockOffset),
  144. p.firstBlockTime.Add(p.height2ProposedBlockOffset+10*blockTimeIota))
  145. }
  146. func (p *pbtsTestHarness) intermediateHeights(ctx context.Context, t *testing.T) {
  147. signer := p.otherValidators[1].PrivValidator
  148. p.nextHeight(ctx, t, signer,
  149. p.firstBlockTime.Add(p.height2ProposedBlockOffset+10*blockTimeIota),
  150. p.firstBlockTime.Add(p.height2ProposedBlockOffset+10*blockTimeIota),
  151. p.firstBlockTime.Add(p.height4ProposedBlockOffset))
  152. signer = p.otherValidators[2].PrivValidator
  153. p.nextHeight(ctx, t, signer,
  154. p.firstBlockTime.Add(p.height4ProposedBlockOffset),
  155. p.firstBlockTime.Add(p.height4ProposedBlockOffset),
  156. time.Now())
  157. }
  158. func (p *pbtsTestHarness) height5(ctx context.Context, t *testing.T) (heightResult, time.Time) {
  159. return p.observedValidatorProposerHeight(ctx, t, p.firstBlockTime.Add(p.height4ProposedBlockOffset))
  160. }
  161. func (p *pbtsTestHarness) nextHeight(ctx context.Context, t *testing.T, proposer types.PrivValidator, deliverTime, proposedTime, nextProposedTime time.Time) heightResult {
  162. p.validatorClock.On("Now").Return(nextProposedTime).Times(6)
  163. ensureNewRound(t, p.roundCh, p.currentHeight, p.currentRound)
  164. b, err := p.observedState.createProposalBlock(ctx)
  165. require.NoError(t, err)
  166. b.Height = p.currentHeight
  167. b.Header.Height = p.currentHeight
  168. b.Header.Time = proposedTime
  169. k, err := proposer.GetPubKey(ctx)
  170. require.NoError(t, err)
  171. b.Header.ProposerAddress = k.Address()
  172. ps, err := b.MakePartSet(types.BlockPartSizeBytes)
  173. require.NoError(t, err)
  174. bid := types.BlockID{Hash: b.Hash(), PartSetHeader: ps.Header()}
  175. prop := types.NewProposal(p.currentHeight, 0, -1, bid, proposedTime)
  176. tp := prop.ToProto()
  177. if err := proposer.SignProposal(ctx, p.observedState.state.ChainID, tp); err != nil {
  178. t.Fatalf("error signing proposal: %s", err)
  179. }
  180. time.Sleep(time.Until(deliverTime))
  181. prop.Signature = tp.Signature
  182. if err := p.observedState.SetProposalAndBlock(ctx, prop, b, ps, "peerID"); err != nil {
  183. t.Fatal(err)
  184. }
  185. ensureProposal(t, p.ensureProposalCh, p.currentHeight, 0, bid)
  186. ensurePrevote(t, p.ensureVoteCh, p.currentHeight, p.currentRound)
  187. signAddVotes(ctx, t, p.observedState, tmproto.PrevoteType, p.chainID, bid, p.otherValidators...)
  188. signAddVotes(ctx, t, p.observedState, tmproto.PrecommitType, p.chainID, bid, p.otherValidators...)
  189. ensurePrecommit(t, p.ensureVoteCh, p.currentHeight, p.currentRound)
  190. vk, err := p.observedValidator.GetPubKey(ctx)
  191. require.NoError(t, err)
  192. res := collectHeightResults(ctx, t, p.eventCh, p.currentHeight, vk.Address())
  193. ensureNewBlock(t, p.blockCh, p.currentHeight)
  194. p.currentHeight++
  195. incrementHeight(p.otherValidators...)
  196. return res
  197. }
  198. func timestampedCollector(ctx context.Context, t *testing.T, eb *eventbus.EventBus) <-chan timestampedEvent {
  199. t.Helper()
  200. // Since eventCh is not read until the end of each height, it must be large
  201. // enough to hold all of the events produced during a single height.
  202. eventCh := make(chan timestampedEvent, 100)
  203. if err := eb.Observe(ctx, func(msg tmpubsub.Message) error {
  204. eventCh <- timestampedEvent{
  205. ts: time.Now(),
  206. m: msg,
  207. }
  208. return nil
  209. }, types.EventQueryVote, types.EventQueryCompleteProposal); err != nil {
  210. t.Fatalf("Failed to observe query %v: %v", types.EventQueryVote, err)
  211. }
  212. return eventCh
  213. }
  214. func collectHeightResults(ctx context.Context, t *testing.T, eventCh <-chan timestampedEvent, height int64, address []byte) heightResult {
  215. t.Helper()
  216. var res heightResult
  217. for event := range eventCh {
  218. switch v := event.m.Data().(type) {
  219. case types.EventDataVote:
  220. if v.Vote.Height > height {
  221. t.Fatalf("received prevote from unexpected height, expected: %d, saw: %d", height, v.Vote.Height)
  222. }
  223. if !bytes.Equal(address, v.Vote.ValidatorAddress) {
  224. continue
  225. }
  226. if v.Vote.Type != tmproto.PrevoteType {
  227. continue
  228. }
  229. res.prevote = v.Vote
  230. res.prevoteIssuedAt = event.ts
  231. case types.EventDataCompleteProposal:
  232. if v.Height > height {
  233. t.Fatalf("received proposal from unexpected height, expected: %d, saw: %d", height, v.Height)
  234. }
  235. res.proposalIssuedAt = event.ts
  236. }
  237. if res.isComplete() {
  238. return res
  239. }
  240. }
  241. t.Fatalf("complete height result never seen for height %d", height)
  242. panic("unreachable")
  243. }
  244. type timestampedEvent struct {
  245. ts time.Time
  246. m tmpubsub.Message
  247. }
  248. func (p *pbtsTestHarness) run(ctx context.Context, t *testing.T) resultSet {
  249. startTestRound(ctx, p.observedState, p.currentHeight, p.currentRound)
  250. r1, proposalBlockTime := p.observedValidatorProposerHeight(ctx, t, p.genesisTime)
  251. p.firstBlockTime = proposalBlockTime
  252. r2 := p.height2(ctx, t)
  253. p.intermediateHeights(ctx, t)
  254. r5, _ := p.height5(ctx, t)
  255. return resultSet{
  256. genesisHeight: r1,
  257. height2: r2,
  258. height5: r5,
  259. }
  260. }
  261. type resultSet struct {
  262. genesisHeight heightResult
  263. height2 heightResult
  264. height5 heightResult
  265. }
  266. type heightResult struct {
  267. proposalIssuedAt time.Time
  268. prevote *types.Vote
  269. prevoteIssuedAt time.Time
  270. }
  271. func (hr heightResult) isComplete() bool {
  272. return !hr.proposalIssuedAt.IsZero() && !hr.prevoteIssuedAt.IsZero() && hr.prevote != nil
  273. }
  274. // TestProposerWaitsForGenesisTime tests that a proposer will not propose a block
  275. // until after the genesis time has passed. The test sets the genesis time in the
  276. // future and then ensures that the observed validator waits to propose a block.
  277. func TestProposerWaitsForGenesisTime(t *testing.T) {
  278. ctx, cancel := context.WithCancel(context.Background())
  279. defer cancel()
  280. // create a genesis time far (enough) in the future.
  281. initialTime := time.Now().Add(800 * time.Millisecond)
  282. cfg := pbtsTestConfiguration{
  283. synchronyParams: types.SynchronyParams{
  284. Precision: 10 * time.Millisecond,
  285. MessageDelay: 10 * time.Millisecond,
  286. },
  287. timeoutPropose: 10 * time.Millisecond,
  288. genesisTime: initialTime,
  289. height2ProposalTimeDeliveryOffset: 10 * time.Millisecond,
  290. height2ProposedBlockOffset: 10 * time.Millisecond,
  291. height4ProposedBlockOffset: 30 * time.Millisecond,
  292. }
  293. pbtsTest := newPBTSTestHarness(ctx, t, cfg)
  294. results := pbtsTest.run(ctx, t)
  295. // ensure that the proposal was issued after the genesis time.
  296. assert.True(t, results.genesisHeight.proposalIssuedAt.After(cfg.genesisTime))
  297. }
  298. // TestProposerWaitsForPreviousBlock tests that the proposer of a block waits until
  299. // the block time of the previous height has passed to propose the next block.
  300. // The test harness ensures that the observed validator will be the proposer at
  301. // height 1 and height 5. The test sets the block time of height 4 in the future
  302. // and then verifies that the observed validator waits until after the block time
  303. // of height 4 to propose a block at height 5.
  304. func TestProposerWaitsForPreviousBlock(t *testing.T) {
  305. ctx, cancel := context.WithCancel(context.Background())
  306. defer cancel()
  307. initialTime := time.Now().Add(time.Millisecond * 50)
  308. cfg := pbtsTestConfiguration{
  309. synchronyParams: types.SynchronyParams{
  310. Precision: 100 * time.Millisecond,
  311. MessageDelay: 500 * time.Millisecond,
  312. },
  313. timeoutPropose: 50 * time.Millisecond,
  314. genesisTime: initialTime,
  315. height2ProposalTimeDeliveryOffset: 150 * time.Millisecond,
  316. height2ProposedBlockOffset: 100 * time.Millisecond,
  317. height4ProposedBlockOffset: 800 * time.Millisecond,
  318. }
  319. pbtsTest := newPBTSTestHarness(ctx, t, cfg)
  320. results := pbtsTest.run(ctx, t)
  321. // the observed validator is the proposer at height 5.
  322. // ensure that the observed validator did not propose a block until after
  323. // the time configured for height 4.
  324. assert.True(t, results.height5.proposalIssuedAt.After(pbtsTest.firstBlockTime.Add(cfg.height4ProposedBlockOffset)))
  325. // Ensure that the validator issued a prevote for a non-nil block.
  326. assert.NotNil(t, results.height5.prevote.BlockID.Hash)
  327. }
  328. func TestProposerWaitTime(t *testing.T) {
  329. genesisTime, err := time.Parse(time.RFC3339, "2019-03-13T23:00:00Z")
  330. require.NoError(t, err)
  331. testCases := []struct {
  332. name string
  333. previousBlockTime time.Time
  334. localTime time.Time
  335. expectedWait time.Duration
  336. }{
  337. {
  338. name: "block time greater than local time",
  339. previousBlockTime: genesisTime.Add(5 * time.Nanosecond),
  340. localTime: genesisTime.Add(1 * time.Nanosecond),
  341. expectedWait: 4 * time.Nanosecond,
  342. },
  343. {
  344. name: "local time greater than block time",
  345. previousBlockTime: genesisTime.Add(1 * time.Nanosecond),
  346. localTime: genesisTime.Add(5 * time.Nanosecond),
  347. expectedWait: 0,
  348. },
  349. {
  350. name: "both times equal",
  351. previousBlockTime: genesisTime.Add(5 * time.Nanosecond),
  352. localTime: genesisTime.Add(5 * time.Nanosecond),
  353. expectedWait: 0,
  354. },
  355. }
  356. for _, testCase := range testCases {
  357. t.Run(testCase.name, func(t *testing.T) {
  358. mockSource := new(tmtimemocks.Source)
  359. mockSource.On("Now").Return(testCase.localTime)
  360. ti := proposerWaitTime(mockSource, testCase.previousBlockTime)
  361. assert.Equal(t, testCase.expectedWait, ti)
  362. })
  363. }
  364. }
  365. func TestTimelyProposal(t *testing.T) {
  366. ctx, cancel := context.WithCancel(context.Background())
  367. defer cancel()
  368. initialTime := time.Now()
  369. cfg := pbtsTestConfiguration{
  370. synchronyParams: types.SynchronyParams{
  371. Precision: 10 * time.Millisecond,
  372. MessageDelay: 140 * time.Millisecond,
  373. },
  374. timeoutPropose: 40 * time.Millisecond,
  375. genesisTime: initialTime,
  376. height2ProposedBlockOffset: 15 * time.Millisecond,
  377. height2ProposalTimeDeliveryOffset: 30 * time.Millisecond,
  378. }
  379. pbtsTest := newPBTSTestHarness(ctx, t, cfg)
  380. results := pbtsTest.run(ctx, t)
  381. require.NotNil(t, results.height2.prevote.BlockID.Hash)
  382. }
  383. func TestTooFarInThePastProposal(t *testing.T) {
  384. ctx, cancel := context.WithCancel(context.Background())
  385. defer cancel()
  386. // localtime > proposedBlockTime + MsgDelay + Precision
  387. cfg := pbtsTestConfiguration{
  388. synchronyParams: types.SynchronyParams{
  389. Precision: 1 * time.Millisecond,
  390. MessageDelay: 10 * time.Millisecond,
  391. },
  392. timeoutPropose: 50 * time.Millisecond,
  393. height2ProposedBlockOffset: 15 * time.Millisecond,
  394. height2ProposalTimeDeliveryOffset: 27 * time.Millisecond,
  395. }
  396. pbtsTest := newPBTSTestHarness(ctx, t, cfg)
  397. results := pbtsTest.run(ctx, t)
  398. require.Nil(t, results.height2.prevote.BlockID.Hash)
  399. }
  400. func TestTooFarInTheFutureProposal(t *testing.T) {
  401. ctx, cancel := context.WithCancel(context.Background())
  402. defer cancel()
  403. // localtime < proposedBlockTime - Precision
  404. cfg := pbtsTestConfiguration{
  405. synchronyParams: types.SynchronyParams{
  406. Precision: 1 * time.Millisecond,
  407. MessageDelay: 10 * time.Millisecond,
  408. },
  409. timeoutPropose: 50 * time.Millisecond,
  410. height2ProposedBlockOffset: 100 * time.Millisecond,
  411. height2ProposalTimeDeliveryOffset: 10 * time.Millisecond,
  412. height4ProposedBlockOffset: 150 * time.Millisecond,
  413. }
  414. pbtsTest := newPBTSTestHarness(ctx, t, cfg)
  415. results := pbtsTest.run(ctx, t)
  416. require.Nil(t, results.height2.prevote.BlockID.Hash)
  417. }