diff --git a/agreement/demux.go b/agreement/demux.go index a0923e5185..11701101fb 100644 --- a/agreement/demux.go +++ b/agreement/demux.go @@ -204,6 +204,11 @@ func (d *demux) next(s *Service, deadline Deadline, fastDeadline time.Duration, e = e.(messageEvent).AttachValidatedAt(s.Clock.Since()) case payloadPresent, votePresent: e = e.(messageEvent).AttachReceivedAt(s.Clock.Since()) + case voteVerified: + // if this is a proposal vote (step 0), record the validatedAt time on the vote + if e.(messageEvent).Input.UnauthenticatedVote.R.Step == 0 { + e = e.(messageEvent).AttachValidatedAt(s.Clock.Since()) + } } }() diff --git a/agreement/events.go b/agreement/events.go index 52737e5f2c..29341f3776 100644 --- a/agreement/events.go +++ b/agreement/events.go @@ -197,6 +197,10 @@ const ( // readPinned is sent to the proposalStore to read the pinned value, if it exists. readPinned + // readLowestVote is sent to the proposalPeriodMachine to read the + // proposal-vote with the lowest credential. + readLowestVote + /* * The following are event types that replace queries, and may warrant * a revision to make them more state-machine-esque. @@ -407,6 +411,34 @@ func (e newRoundEvent) ComparableStr() string { return e.String() } +type readLowestEvent struct { + // T currently only supports readLowestVote + T eventType + + // Round and Period are the round and period for which to query + // the lowest-credential vote, value or payload. This type of event + // is only sent for pipelining, which only makes sense for period + // 0, but the Period is here anyway to route to the appropriate + // proposalMachinePeriod. + Round round + Period period + + // Vote holds the lowest-credential vote. + Vote vote +} + +func (e readLowestEvent) t() eventType { + return e.T +} + +func (e readLowestEvent) String() string { + return fmt.Sprintf("%s: %d %d", e.t().String(), e.Round, e.Period) +} + +func (e readLowestEvent) ComparableStr() string { + return e.String() +} + type newPeriodEvent struct { // Period holds the latest period relevant to the proposalRoundMachine. Period period @@ -942,7 +974,12 @@ func (e checkpointEvent) AttachConsensusVersion(v ConsensusVersionView) external } func (e messageEvent) AttachValidatedAt(d time.Duration) messageEvent { - e.Input.Proposal.validatedAt = d + switch e.T { + case payloadVerified: + e.Input.Proposal.validatedAt = d + case voteVerified: + e.Input.Vote.validatedAt = d + } return e } diff --git a/agreement/eventtype_string.go b/agreement/eventtype_string.go index 9da84c1b98..9973215b12 100644 --- a/agreement/eventtype_string.go +++ b/agreement/eventtype_string.go @@ -37,21 +37,22 @@ func _() { _ = x[newPeriod-26] _ = x[readStaging-27] _ = x[readPinned-28] - _ = x[voteFilterRequest-29] - _ = x[voteFilteredStep-30] - _ = x[nextThresholdStatusRequest-31] - _ = x[nextThresholdStatus-32] - _ = x[freshestBundleRequest-33] - _ = x[freshestBundle-34] - _ = x[dumpVotesRequest-35] - _ = x[dumpVotes-36] - _ = x[wrappedAction-37] - _ = x[checkpointReached-38] + _ = x[readLowestVote-29] + _ = x[voteFilterRequest-30] + _ = x[voteFilteredStep-31] + _ = x[nextThresholdStatusRequest-32] + _ = x[nextThresholdStatus-33] + _ = x[freshestBundleRequest-34] + _ = x[freshestBundle-35] + _ = x[dumpVotesRequest-36] + _ = x[dumpVotes-37] + _ = x[wrappedAction-38] + _ = x[checkpointReached-39] } -const _eventType_name = "nonevotePresentpayloadPresentbundlePresentvoteVerifiedpayloadVerifiedbundleVerifiedroundInterruptiontimeoutfastTimeoutsoftThresholdcertThresholdnextThresholdproposalCommittableproposalAcceptedvoteFilteredvoteMalformedbundleFilteredbundleMalformedpayloadRejectedpayloadMalformedpayloadPipelinedpayloadAcceptedproposalFrozenvoteAcceptednewRoundnewPeriodreadStagingreadPinnedvoteFilterRequestvoteFilteredStepnextThresholdStatusRequestnextThresholdStatusfreshestBundleRequestfreshestBundledumpVotesRequestdumpVoteswrappedActioncheckpointReached" +const _eventType_name = "nonevotePresentpayloadPresentbundlePresentvoteVerifiedpayloadVerifiedbundleVerifiedroundInterruptiontimeoutfastTimeoutsoftThresholdcertThresholdnextThresholdproposalCommittableproposalAcceptedvoteFilteredvoteMalformedbundleFilteredbundleMalformedpayloadRejectedpayloadMalformedpayloadPipelinedpayloadAcceptedproposalFrozenvoteAcceptednewRoundnewPeriodreadStagingreadPinnedreadLowestVotevoteFilterRequestvoteFilteredStepnextThresholdStatusRequestnextThresholdStatusfreshestBundleRequestfreshestBundledumpVotesRequestdumpVoteswrappedActioncheckpointReached" -var _eventType_index = [...]uint16{0, 4, 15, 29, 42, 54, 69, 83, 100, 107, 118, 131, 144, 157, 176, 192, 204, 217, 231, 246, 261, 277, 293, 308, 322, 334, 342, 351, 362, 372, 389, 405, 431, 450, 471, 485, 501, 510, 523, 540} +var _eventType_index = [...]uint16{0, 4, 15, 29, 42, 54, 69, 83, 100, 107, 118, 131, 144, 157, 176, 192, 204, 217, 231, 246, 261, 277, 293, 308, 322, 334, 342, 351, 362, 372, 386, 403, 419, 445, 464, 485, 499, 515, 524, 537, 554} func (i eventType) String() string { if i >= eventType(len(_eventType_index)-1) { diff --git a/agreement/msgp_gen.go b/agreement/msgp_gen.go index ee65757f52..d8de90ff82 100644 --- a/agreement/msgp_gen.go +++ b/agreement/msgp_gen.go @@ -4259,7 +4259,7 @@ func (z *proposal) MarshalMsg(b []byte) (o []byte) { o = msgp.Require(b, z.Msgsize()) // omitempty: check for empty values zb0004Len := uint32(29) - var zb0004Mask uint64 /* 38 bits */ + var zb0004Mask uint64 /* 39 bits */ if (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsLevel == 0 { zb0004Len-- zb0004Mask |= 0x40 @@ -11192,7 +11192,7 @@ func (z *vote) MarshalMsg(b []byte) (o []byte) { o = msgp.Require(b, z.Msgsize()) // omitempty: check for empty values zb0001Len := uint32(3) - var zb0001Mask uint8 /* 4 bits */ + var zb0001Mask uint8 /* 5 bits */ if (*z).Cred.MsgIsZero() { zb0001Len-- zb0001Mask |= 0x2 diff --git a/agreement/player.go b/agreement/player.go index dd038d835b..15a68316b8 100644 --- a/agreement/player.go +++ b/agreement/player.go @@ -63,7 +63,9 @@ type player struct { // must be verified after some vote has been verified. Pending proposalTable - payloadArrivals []time.Duration + // the history of arrival times of the lowest credential from previous + // ronuds, used for calculating dynamic lambda. + lowestCredentialArrivals []time.Duration } func (p *player) T() stateMachineTag { @@ -275,19 +277,29 @@ func (p *player) handleCheckpointEvent(r routerHandle, e checkpointEvent) []acti }} } -func (p *player) handleWinningPayloadArrival(payload proposal, ver protocol.ConsensusVersion) { - // ignoring validatedAt - p.payloadArrivals = append(p.payloadArrivals, payload.receivedAt) - p.resizePayloadArrivals(ver) +// updateDynamicLambdaTimings is called at the end of a successful uninterrupted round (just after ensureAction +// is generated) to collect round timings for updating dynamic lambda. +func (p *player) updateDynamicLambdaTimings(r routerHandle, ver protocol.ConsensusVersion) { + // only append to lowestCredentialArrivals if this was a successful round completing in period 0. + if p.Period != 0 { + return + } + // look up the validatedAt time of the winning proposal-vote + re := readLowestEvent{T: readLowestVote, Round: p.Round, Period: p.Period} + re = r.dispatch(*p, re, proposalMachineRound, p.Round, p.Period, 0).(readLowestEvent) + if re.Vote.validatedAt != 0 { + p.lowestCredentialArrivals = append(p.lowestCredentialArrivals, re.Vote.validatedAt) + } + p.resizeLowestCredentialArrivals(ver) } -func (p *player) resizePayloadArrivals(ver protocol.ConsensusVersion) { +func (p *player) resizeLowestCredentialArrivals(ver protocol.ConsensusVersion) { proto := config.Consensus[ver] - if len(p.payloadArrivals) > proto.DynamicFilterPayloadArriavalHistory { - p.payloadArrivals = p.payloadArrivals[len(p.payloadArrivals)-proto.DynamicFilterPayloadArriavalHistory:] + if len(p.lowestCredentialArrivals) > proto.DynamicFilterPayloadArriavalHistory { + p.lowestCredentialArrivals = p.lowestCredentialArrivals[len(p.lowestCredentialArrivals)-proto.DynamicFilterPayloadArriavalHistory:] } - for len(p.payloadArrivals) < proto.DynamicFilterPayloadArriavalHistory { - p.payloadArrivals = append([]time.Duration{FilterTimeout(0, ver)}, p.payloadArrivals...) + for len(p.lowestCredentialArrivals) < proto.DynamicFilterPayloadArriavalHistory { + p.lowestCredentialArrivals = append([]time.Duration{FilterTimeout(0, ver)}, p.lowestCredentialArrivals...) } } @@ -307,12 +319,12 @@ func (p *player) calculateFilterTimeout(ver protocol.ConsensusVersion, tracer *t if proto.DynamicFilterPayloadArriavalHistory <= 0 { // we don't keep any history, use the default dynamicDelay = defaultDelay - } else if proto.DynamicFilterPayloadArriavalHistory > len(p.payloadArrivals) { + } else if proto.DynamicFilterPayloadArriavalHistory > len(p.lowestCredentialArrivals) { // not enough samples, use the default dynamicDelay = defaultDelay } else { - sortedArrivals := make([]time.Duration, len(p.payloadArrivals)) - copy(sortedArrivals[:], p.payloadArrivals[:]) + sortedArrivals := make([]time.Duration, len(p.lowestCredentialArrivals)) + copy(sortedArrivals[:], p.lowestCredentialArrivals[:]) sort.Slice(sortedArrivals, func(i, j int) bool { return sortedArrivals[i] < sortedArrivals[j] }) dynamicDelay = sortedArrivals[proto.DynamicFilterPayloadArriavalHistory-1] } @@ -348,7 +360,7 @@ func (p *player) handleThresholdEvent(r routerHandle, e thresholdEvent) []action cert := Certificate(e.Bundle) a0 := ensureAction{Payload: res.Payload, Certificate: cert} actions = append(actions, a0) - p.handleWinningPayloadArrival(res.Payload, e.Proto) + p.updateDynamicLambdaTimings(r, e.Proto) as := p.enterRound(r, e, p.Round+1) return append(actions, as...) } @@ -405,7 +417,7 @@ func (p *player) enterPeriod(r routerHandle, source thresholdEvent, target perio if target != 0 { // We entered a non-0 period, we should reset the filter timeout // calculation mechanism. - p.payloadArrivals = make([]time.Duration, 0) + p.lowestCredentialArrivals = make([]time.Duration, 0) } p.Deadline.Duration = p.calculateFilterTimeout(source.Proto, r.t) p.Deadline.Type = timers.Filter @@ -678,7 +690,7 @@ func (p *player) handleMessageEvent(r routerHandle, e messageEvent) (actions []a cert := Certificate(freshestRes.Event.Bundle) a0 := ensureAction{Payload: e.Input.Proposal, Certificate: cert} actions = append(actions, a0) - p.handleWinningPayloadArrival(e.Input.Proposal, e.Proto.Version) + p.updateDynamicLambdaTimings(r, e.Proto.Version) as := p.enterRound(r, delegatedE, cert.Round+1) return append(actions, as...) } diff --git a/agreement/player_test.go b/agreement/player_test.go index 9a48264470..5662064a96 100644 --- a/agreement/player_test.go +++ b/agreement/player_test.go @@ -3235,19 +3235,20 @@ func TestPlayerAlwaysResynchsPinnedValue(t *testing.T) { } // test that ReceivedAt and ValidateAt timing information are retained in proposalStore -// when the payloadPresent and payloadVerified events are processed, and that both timings +// when the payloadPresent, payloadVerified, and voteVerified events are processed, and that all timings // are available when the ensureAction is called for the block. func TestPlayerRetainsReceivedValidatedAt(t *testing.T) { partitiontest.PartitionTest(t) const r = round(20239) - const p = period(1001) + const p = period(0) pWhite, pM, helper := setupP(t, r-1, p, soft) pP, pV := helper.MakeRandomProposalPayload(t, r-1) // send voteVerified message vVote := helper.MakeVerifiedVote(t, 0, r-1, p, propose, *pV) inMsg := messageEvent{T: voteVerified, Input: message{Vote: vVote, UnauthenticatedVote: vVote.u()}} + inMsg = inMsg.AttachValidatedAt(501 * time.Millisecond) err, panicErr := pM.transition(inMsg) require.NoError(t, err) require.NoError(t, panicErr) @@ -3260,26 +3261,32 @@ func TestPlayerRetainsReceivedValidatedAt(t *testing.T) { require.NoError(t, err) require.NoError(t, panicErr) - assertCorrectReceivedAtSet(t, pWhite, pM, helper, r, p, pP, pV, m) + assertCorrectReceivedAtSet(t, pWhite, pM, helper, r, p, pP, pV, m, protocol.ConsensusFuture) + + // assert lowest vote validateAt time was recorded into payloadArrivals + historyLen := config.Consensus[protocol.ConsensusFuture].DynamicFilterPayloadArriavalHistory + require.NotZero(t, historyLen) + require.Len(t, pWhite.lowestCredentialArrivals, historyLen) + require.Equal(t, 501*time.Millisecond, pWhite.lowestCredentialArrivals[historyLen-1]) } // test that ReceivedAt and ValidateAt timing information are retained in proposalStore -// when the payloadPresent (as part of the CompoundMessage encoding used by PP messages) -// and payloadVerified events are processed, and that both timings +// when the payloadPresent (as part of the CompoundMessage encoding used by PP messages), +// payloadVerified, and voteVerified events are processed, and that all timings // are available when the ensureAction is called for the block. func TestPlayerRetainsReceivedValidatedAtPP(t *testing.T) { partitiontest.PartitionTest(t) const r = round(20239) - const p = period(1001) + const p = period(0) pWhite, pM, helper := setupP(t, r-1, p, soft) pP, pV := helper.MakeRandomProposalPayload(t, r-1) // create a PP message for an arbitrary proposal/payload similar to setupCompoundMessage vVote := helper.MakeVerifiedVote(t, 0, r-1, p, propose, *pV) - voteMsg := message{Vote: vVote, UnauthenticatedVote: vVote.u()} + unverifiedVoteMsg := message{UnauthenticatedVote: vVote.u()} proposalMsg := message{UnauthenticatedProposal: pP.u()} - compoundMsg := messageEvent{T: votePresent, Input: voteMsg, + compoundMsg := messageEvent{T: votePresent, Input: unverifiedVoteMsg, Tail: &messageEvent{T: payloadPresent, Input: proposalMsg}} inMsg := compoundMsg.AttachReceivedAt(time.Second) // call AttachReceivedAt like demux would err, panicErr := pM.transition(inMsg) @@ -3287,34 +3294,97 @@ func TestPlayerRetainsReceivedValidatedAtPP(t *testing.T) { require.NoError(t, panicErr) // make sure vote verify requests - verifyEvent := ev(cryptoAction{T: verifyVote, M: voteMsg, Round: r - 1, Period: p, Step: propose, TaskIndex: 1}) + verifyEvent := ev(cryptoAction{T: verifyVote, M: unverifiedVoteMsg, Round: r - 1, Period: p, Step: propose, TaskIndex: 1}) + require.Truef(t, pM.getTrace().Contains(verifyEvent), "Player should verify vote") + + // send voteVerified + verifiedVoteMsg := message{Vote: vVote, UnauthenticatedVote: vVote.u()} + inMsg = messageEvent{T: voteVerified, Input: verifiedVoteMsg, TaskIndex: 1} + inMsg = inMsg.AttachValidatedAt(502 * time.Millisecond) + err, panicErr = pM.transition(inMsg) + require.NoError(t, err) + require.NoError(t, panicErr) + + assertCorrectReceivedAtSet(t, pWhite, pM, helper, r, p, pP, pV, proposalMsg, protocol.ConsensusFuture) + + // assert lowest vote validateAt time was recorded into payloadArrivals + historyLen := config.Consensus[protocol.ConsensusFuture].DynamicFilterPayloadArriavalHistory + require.NotZero(t, historyLen) + require.Len(t, pWhite.lowestCredentialArrivals, historyLen) + require.Equal(t, 502*time.Millisecond, pWhite.lowestCredentialArrivals[historyLen-1]) +} + +// test that ReceivedAt and ValidateAt timing information are retained in proposalStore +// when the voteVerified event comes in first (as part of the AV message before PP), +// then the payloadPresent (as part of the CompoundMessage encoding used by PP messages) +// and payloadVerified events are processed, and that all timings +// are available when the ensureAction is called for the block. +func TestPlayerRetainsReceivedValidatedAtAVPP(t *testing.T) { + partitiontest.PartitionTest(t) + + const r = round(20239) + const p = period(0) + pWhite, pM, helper := setupP(t, r-1, p, soft) + pP, pV := helper.MakeRandomProposalPayload(t, r-1) + + // send votePresent message (mimicking the first AV message validating) + vVote := helper.MakeVerifiedVote(t, 0, r-1, p, propose, *pV) + unverifiedVoteMsg := message{UnauthenticatedVote: vVote.u()} + inMsg := messageEvent{T: votePresent, Input: unverifiedVoteMsg} + err, panicErr := pM.transition(inMsg) + require.NoError(t, err) + require.NoError(t, panicErr) + + // make sure vote verify requests + verifyEvent := ev(cryptoAction{T: verifyVote, M: unverifiedVoteMsg, Round: r - 1, Period: p, Step: propose, TaskIndex: 1}) require.Truef(t, pM.getTrace().Contains(verifyEvent), "Player should verify vote") // send voteVerified - inMsg = messageEvent{T: voteVerified, Input: voteMsg, TaskIndex: 1} + verifiedVoteMsg := message{Vote: vVote, UnauthenticatedVote: vVote.u()} + inMsg = messageEvent{T: voteVerified, Input: verifiedVoteMsg, TaskIndex: 1} + inMsg = inMsg.AttachValidatedAt(502 * time.Millisecond) err, panicErr = pM.transition(inMsg) require.NoError(t, err) require.NoError(t, panicErr) - assertCorrectReceivedAtSet(t, pWhite, pM, helper, r, p, pP, pV, proposalMsg) + // create a PP message for an arbitrary proposal/payload similar to setupCompoundMessage + proposalMsg := message{UnauthenticatedProposal: pP.u()} + compoundMsg := messageEvent{T: votePresent, Input: unverifiedVoteMsg, + Tail: &messageEvent{T: payloadPresent, Input: proposalMsg}} + inMsg = compoundMsg.AttachReceivedAt(time.Second) // call AttachReceivedAt like demux would + err, panicErr = pM.transition(inMsg) + require.NoError(t, err) + require.NoError(t, panicErr) + + // make sure no second request to verify this vote + verifyEvent = ev(cryptoAction{T: verifyVote, M: unverifiedVoteMsg, Round: r - 1, Period: p, Step: propose, TaskIndex: 1}) + require.Equal(t, 1, pM.getTrace().CountEvent(verifyEvent), "Player should not verify second vote") + + assertCorrectReceivedAtSet(t, pWhite, pM, helper, r, p, pP, pV, proposalMsg, protocol.ConsensusFuture) + + // assert lowest vote validateAt time was recorded into payloadArrivals + historyLen := config.Consensus[protocol.ConsensusFuture].DynamicFilterPayloadArriavalHistory + require.NotZero(t, historyLen) + require.Len(t, pWhite.lowestCredentialArrivals, historyLen) + require.Equal(t, 502*time.Millisecond, pWhite.lowestCredentialArrivals[historyLen-1]) } func assertCorrectReceivedAtSet(t *testing.T, pWhite *player, pM ioAutomata, helper *voteMakerHelper, - r round, p period, pP *proposal, pV *proposalValue, m message) { + r round, p period, pP *proposal, pV *proposalValue, m message, ver protocol.ConsensusVersion) { // make sure payload verify request verifyEvent := ev(cryptoAction{T: verifyPayload, M: m, Round: r - 1, Period: p, Step: propose, TaskIndex: 0}) require.Truef(t, pM.getTrace().Contains(verifyEvent), "Player should verify payload") // payloadVerified - inMsg := messageEvent{T: payloadVerified, Input: message{Proposal: *pP}, Proto: ConsensusVersionView{Version: protocol.ConsensusCurrentVersion}} + inMsg := messageEvent{T: payloadVerified, Input: message{Proposal: *pP}, Proto: ConsensusVersionView{Version: ver}} inMsg = inMsg.AttachValidatedAt(2 * time.Second) // call AttachValidatedAt like demux would err, panicErr := pM.transition(inMsg) require.NoError(t, err) require.NoError(t, panicErr) // gen cert to move into the next round - votes := make([]vote, int(cert.threshold(config.Consensus[protocol.ConsensusCurrentVersion]))) - for i := 0; i < int(cert.threshold(config.Consensus[protocol.ConsensusCurrentVersion])); i++ { + votes := make([]vote, int(cert.threshold(config.Consensus[ver]))) + for i := 0; i < int(cert.threshold(config.Consensus[ver])); i++ { votes[i] = helper.MakeVerifiedVote(t, i, r-1, p, cert, *pV) } bun := unauthenticatedBundle{ @@ -3331,7 +3401,7 @@ func assertCorrectReceivedAtSet(t *testing.T, pWhite *player, pM ioAutomata, hel }, UnauthenticatedBundle: bun, }, - Proto: ConsensusVersionView{Version: protocol.ConsensusCurrentVersion}, + Proto: ConsensusVersionView{Version: ver}, } err, panicErr = pM.transition(inMsg) require.NoError(t, err) diff --git a/agreement/proposal.go b/agreement/proposal.go index bf021f2cfe..f2dde4bd6a 100644 --- a/agreement/proposal.go +++ b/agreement/proposal.go @@ -99,6 +99,8 @@ type proposal struct { // validated (and thus was ready to be delivered to the state // machine), relative to the zero of that round. validatedAt time.Duration + + voteValidatedAt time.Duration } func makeProposal(ve ValidatedBlock, pf crypto.VrfProof, origPer period, origProp basics.Address) proposal { diff --git a/agreement/proposalStore.go b/agreement/proposalStore.go index 080609de50..fdfecac5f2 100644 --- a/agreement/proposalStore.go +++ b/agreement/proposalStore.go @@ -352,6 +352,9 @@ func (store *proposalStore) handle(r routerHandle, p player, e event) event { se.Committable = ea.Assembled se.Payload = ea.Payload return se + case readLowestVote: + re := e.(readLowestEvent) + return r.dispatch(p, re, proposalMachinePeriod, re.Round, re.Period, 0).(readLowestEvent) case readPinned: se := e.(pinnedValueEvent) ea := store.Assemblers[store.Pinned] // If pinned is bottom, assembled/payloadOK = false, payload = bottom diff --git a/agreement/proposalTracker.go b/agreement/proposalTracker.go index 59ffb77a28..b9707d54dc 100644 --- a/agreement/proposalTracker.go +++ b/agreement/proposalTracker.go @@ -165,6 +165,11 @@ func (t *proposalTracker) handle(r routerHandle, p player, e event) event { t.Freezer = t.Freezer.freeze() return e + case readLowestVote: + e := e.(readLowestEvent) + e.Vote = t.Freezer.Lowest + return e + case softThreshold, certThreshold: e := e.(thresholdEvent) t.Staging = e.Proposal diff --git a/agreement/proposalTrackerContract.go b/agreement/proposalTrackerContract.go index 2b995dfcac..a6fa2ffc36 100644 --- a/agreement/proposalTrackerContract.go +++ b/agreement/proposalTrackerContract.go @@ -32,7 +32,7 @@ type proposalTrackerContract struct { // TODO check concrete types of events func (c *proposalTrackerContract) pre(p player, in event) (pre []error) { switch in.t() { - case voteVerified, proposalFrozen, softThreshold, certThreshold, voteFilterRequest, readStaging: + case voteVerified, proposalFrozen, softThreshold, certThreshold, voteFilterRequest, readStaging, readLowestVote: default: pre = append(pre, fmt.Errorf("incoming event has invalid type: %v", in.t())) } diff --git a/agreement/state_machine_test.go b/agreement/state_machine_test.go index 0effd9fda1..69b31a9866 100644 --- a/agreement/state_machine_test.go +++ b/agreement/state_machine_test.go @@ -140,6 +140,15 @@ func (t ioTrace) Contains(e event) bool { }) } +func (t ioTrace) CountEvent(b event) (count int) { + for _, e := range t.events { + if e.ComparableStr() == b.ComparableStr() { + count++ + } + } + return +} + // for each event, passes it into the given fn; if returns true, returns true. func (t ioTrace) ContainsFn(compareFn func(b event) bool) bool { for _, ev := range t.events { diff --git a/agreement/vote.go b/agreement/vote.go index af157ee3bb..bed20fe88f 100644 --- a/agreement/vote.go +++ b/agreement/vote.go @@ -18,6 +18,7 @@ package agreement import ( "fmt" + "time" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" @@ -51,6 +52,10 @@ type ( R rawVote `codec:"r"` Cred committee.Credential `codec:"cred"` Sig crypto.OneTimeSignature `codec:"sig,omitempty,omitemptycheckstruct"` + + // validatedAt indicates the time at which this vote was verified (as a voteVerified messageEvent), + // relative to the zero of that round. It is only set for step 0. + validatedAt time.Duration } // unauthenticatedEquivocationVote is a pair of votes which has not