10 lru "github.com/hashicorp/golang-lru"
11 "github.com/vapor/config"
12 "github.com/vapor/protocol"
13 "github.com/vapor/protocol/bc"
14 "github.com/vapor/protocol/bc/types"
18 defaultFullCredit = 1000 // no punished
19 missingPublishCredit = 100 // punished for missing one block seal
20 signRewardCredit = 10 // seal one block
21 autoRewardCredit = 1 // credit auto recover for each block
22 minCalSignerQueueCredit = 300 // when calculate the signerQueue
23 defaultOfficialMaxSignerCount = 21 // official max signer count
24 defaultOfficialFirstLevelCount = 10 // official first level , 100% in signer queue
25 defaultOfficialSecondLevelCount = 20 // official second level, 60% in signer queue
26 defaultOfficialThirdLevelCount = 30 // official third level, 40% in signer queue
27 // the credit of one signer is at least minCalSignerQueueCredit
28 candidateStateNormal = 1
29 candidateMaxLen = 500 // if candidateNeedPD is false and candidate is more than candidateMaxLen, then minimum tickets candidates will be remove in each LCRS*loop
32 var errIncorrectTallyCount = errors.New("incorrect tally count")
34 type Snapshot struct {
35 config *config.DposConfig // Consensus engine configuration parameters
36 sigcache *lru.ARCCache // Cache of recent block signatures to speed up ecrecover
37 LCRS uint64 // Loop count to recreate signers from top tally
38 Period uint64 `json:"period"` // Period of seal each block
39 Number uint64 `json:"number"` // Block Number where the snapshot was created
40 ConfirmedNumber uint64 `json:"confirmed_number"` // Block Number confirmed when the snapshot was created
41 Hash bc.Hash `json:"hash"` // Block hash where the snapshot was created
42 HistoryHash []bc.Hash `json:"historyHash"` // Block hash list for two recent loop
43 Signers []*string `json:"signers"` // Signers queue in current header
44 Votes map[string]*Vote `json:"votes"` // All validate votes from genesis block
45 Tally map[string]uint64 `json:"tally"` // Stake for each candidate address
46 Voters map[string]uint64 `json:"voters"` // Block height for each voter address
47 Candidates map[string]uint64 `json:"candidates"` // Candidates for Signers (0- adding procedure 1- normal 2- removing procedure)
48 Punished map[string]uint64 `json:"punished"` // The signer be punished count cause of missing seal
49 Confirmations map[uint64][]string `json:"confirms"` // The signer confirm given block height
50 Proposals map[bc.Hash]*Proposal `json:"proposals"` // The Proposals going or success (failed proposal will be removed)
51 HeaderTime uint64 `json:"headerTime"` // Time of the current header
52 LoopStartTime uint64 `json:"loopStartTime"` // Start Time of the current loop
55 // newSnapshot creates a new snapshot with the specified startup parameters. only ever use if for
57 func newSnapshot(config *config.DposConfig, sigcache *lru.ARCCache, hash bc.Hash, votes []*Vote, lcrs uint64) *Snapshot {
63 Period: config.Period,
67 HistoryHash: []bc.Hash{},
69 Votes: make(map[string]*Vote),
70 Tally: make(map[string]uint64),
71 Voters: make(map[string]uint64),
72 Punished: make(map[string]uint64),
73 Candidates: make(map[string]uint64),
74 Confirmations: make(map[uint64][]string),
75 Proposals: make(map[bc.Hash]*Proposal),
76 HeaderTime: uint64(time.Now().Unix()) - 1,
77 LoopStartTime: config.GenesisTimestamp,
79 snap.HistoryHash = append(snap.HistoryHash, hash)
80 for _, vote := range votes {
81 // init Votes from each vote
82 snap.Votes[vote.Voter] = vote
84 _, ok := snap.Tally[vote.Candidate]
86 snap.Tally[vote.Candidate] = 0
88 snap.Tally[vote.Candidate] += vote.Stake
90 snap.Voters[vote.Voter] = 0 // block height is 0 , vote in genesis block
92 snap.Candidates[vote.Voter] = candidateStateNormal
95 for i := 0; i < int(config.MaxSignerCount); i++ {
96 snap.Signers = append(snap.Signers, &config.SelfVoteSigners[i%len(config.SelfVoteSigners)])
102 // loadSnapshot loads an existing snapshot from the database.
103 func loadSnapshot(config *config.DposConfig, sigcache *lru.ARCCache, store protocol.Store, hash bc.Hash) (*Snapshot, error) {
104 data, err := store.Get(&hash)
108 snap := new(Snapshot)
109 if err := json.Unmarshal(data, snap); err != nil {
113 snap.sigcache = sigcache
117 // store inserts the snapshot into the database.
118 func (s *Snapshot) store(store protocol.Store) error {
119 data, err := json.Marshal(s)
123 return store.Set(&s.Hash, data)
126 // copy creates a deep copy of the snapshot, though not the individual votes.
127 func (s *Snapshot) copy() *Snapshot {
130 sigcache: s.sigcache,
134 ConfirmedNumber: s.ConfirmedNumber,
136 HistoryHash: make([]bc.Hash, len(s.HistoryHash)),
138 Signers: make([]*string, len(s.Signers)),
139 Votes: make(map[string]*Vote),
140 Tally: make(map[string]uint64),
141 Voters: make(map[string]uint64),
142 Candidates: make(map[string]uint64),
143 Punished: make(map[string]uint64),
144 Proposals: make(map[bc.Hash]*Proposal),
145 Confirmations: make(map[uint64][]string),
147 HeaderTime: s.HeaderTime,
148 LoopStartTime: s.LoopStartTime,
150 copy(cpy.HistoryHash, s.HistoryHash)
151 copy(cpy.Signers, s.Signers)
152 for voter, vote := range s.Votes {
153 cpy.Votes[voter] = &Vote{
155 Candidate: vote.Candidate,
159 for candidate, tally := range s.Tally {
160 cpy.Tally[candidate] = tally
162 for voter, number := range s.Voters {
163 cpy.Voters[voter] = number
165 for candidate, state := range s.Candidates {
166 cpy.Candidates[candidate] = state
168 for signer, cnt := range s.Punished {
169 cpy.Punished[signer] = cnt
171 for blockNumber, confirmers := range s.Confirmations {
172 cpy.Confirmations[blockNumber] = make([]string, len(confirmers))
173 copy(cpy.Confirmations[blockNumber], confirmers)
175 for txHash, proposal := range s.Proposals {
176 cpy.Proposals[txHash] = proposal.copy()
182 // apply creates a new authorization snapshot by applying the given headers to
184 func (s *Snapshot) apply(headers []*types.BlockHeader) (*Snapshot, error) {
185 // Allow passing in no headers for cleaner code
186 if len(headers) == 0 {
189 // Sanity check that the headers can be applied
190 for i := 0; i < len(headers)-1; i++ {
191 if headers[i+1].Height != headers[i].Height+1 {
192 return nil, errInvalidVotingChain
195 if headers[0].Height != s.Number+1 {
196 return nil, errInvalidVotingChain
198 // Iterate through the headers and create a new snapshot
200 for _, header := range headers {
202 // Resolve the authorization key and check against signers
203 coinbase, err := ecrecover(header, s.sigcache, nil)
208 headerExtra := HeaderExtra{}
209 if err := json.Unmarshal(header.Extra[extraVanity:len(header.Extra)-extraSeal], &headerExtra); err != nil {
213 snap.HeaderTime = header.Timestamp
214 snap.LoopStartTime = headerExtra.LoopStartTime
216 for i := range headerExtra.SignerQueue {
217 snap.Signers = append(snap.Signers, &headerExtra.SignerQueue[i])
220 snap.ConfirmedNumber = headerExtra.ConfirmedBlockNumber
222 if len(snap.HistoryHash) >= int(s.config.MaxSignerCount)*2 {
223 snap.HistoryHash = snap.HistoryHash[1 : int(s.config.MaxSignerCount)*2]
226 snap.HistoryHash = append(snap.HistoryHash, header.Hash())
228 // deal the new confirmation in this block
229 snap.updateSnapshotByConfirmations(headerExtra.CurrentBlockConfirmations)
231 // deal the new vote from voter
232 snap.updateSnapshotByVotes(headerExtra.CurrentBlockVotes, header.Height)
234 // deal the snap related with punished
235 snap.updateSnapshotForPunish(headerExtra.SignerMissing, header.Height, coinbase)
238 snap.updateSnapshotByProposals(headerExtra.CurrentBlockProposals, header.Height)
241 snap.updateSnapshotByDeclares(headerExtra.CurrentBlockDeclares, header.Height)
243 // calculate proposal result
244 snap.calculateProposalResult(header.Height)
246 // check the len of candidate if not candidateNeedPD
247 if !candidateNeedPD && (snap.Number+1)%(snap.config.MaxSignerCount*snap.LCRS) == 0 && len(snap.Candidates) > candidateMaxLen {
248 snap.removeExtraCandidate()
252 snap.Number += uint64(len(headers))
253 snap.Hash = headers[len(headers)-1].Hash()
254 snap.updateSnapshotForExpired()
255 err := snap.verifyTallyCnt()
262 func (s *Snapshot) removeExtraCandidate() {
263 // remove minimum tickets tally beyond candidateMaxLen
264 tallySlice := s.buildTallySlice()
265 sort.Sort(TallySlice(tallySlice))
266 if len(tallySlice) > candidateMaxLen {
267 removeNeedTally := tallySlice[candidateMaxLen:]
268 for _, tallySlice := range removeNeedTally {
269 delete(s.Candidates, tallySlice.addr)
274 func (s *Snapshot) verifyTallyCnt() error {
276 tallyTarget := make(map[string]uint64)
277 for _, v := range s.Votes {
278 if _, ok := tallyTarget[v.Candidate]; ok {
279 tallyTarget[v.Candidate] = tallyTarget[v.Candidate] + v.Stake
281 tallyTarget[v.Candidate] = v.Stake
284 for address, tally := range s.Tally {
285 if targetTally, ok := tallyTarget[address]; ok && targetTally == tally {
288 return errIncorrectTallyCount
295 func (s *Snapshot) updateSnapshotByDeclares(declares []Declare, headerHeight uint64) {
296 for _, declare := range declares {
297 if proposal, ok := s.Proposals[declare.ProposalHash]; ok {
298 // check the proposal enable status and valid block number
299 if proposal.ReceivedNumber+proposal.ValidationLoopCnt*s.config.MaxSignerCount < headerHeight || !s.isCandidate(declare.Declarer) {
302 // check if this signer already declare on this proposal
303 alreadyDeclare := false
304 for _, v := range proposal.Declares {
305 if v.Declarer == declare.Declarer {
306 // this declarer already declare for this proposal
307 alreadyDeclare = true
314 // add declare to proposal
315 s.Proposals[declare.ProposalHash].Declares = append(s.Proposals[declare.ProposalHash].Declares,
316 &Declare{declare.ProposalHash, declare.Declarer, declare.Decision})
322 func (s *Snapshot) calculateProposalResult(headerHeight uint64) {
324 for hashKey, proposal := range s.Proposals {
325 // the result will be calculate at receiverdNumber + vlcnt + 1
326 if proposal.ReceivedNumber+proposal.ValidationLoopCnt*s.config.MaxSignerCount+1 == headerHeight {
327 // calculate the current stake of this proposal
328 judegmentStake := big.NewInt(0)
329 for _, tally := range s.Tally {
330 judegmentStake.Add(judegmentStake, new(big.Int).SetUint64(tally))
332 judegmentStake.Mul(judegmentStake, big.NewInt(2))
333 judegmentStake.Div(judegmentStake, big.NewInt(3))
334 // calculate declare stake
335 yesDeclareStake := big.NewInt(0)
336 for _, declare := range proposal.Declares {
337 if declare.Decision {
338 if _, ok := s.Tally[declare.Declarer]; ok {
339 yesDeclareStake.Add(yesDeclareStake, new(big.Int).SetUint64(s.Tally[declare.Declarer]))
343 if yesDeclareStake.Cmp(judegmentStake) > 0 {
344 // process add candidate
345 switch proposal.ProposalType {
346 case proposalTypeCandidateAdd:
348 s.Candidates[s.Proposals[hashKey].Candidate] = candidateStateNormal
350 case proposalTypeCandidateRemove:
351 if _, ok := s.Candidates[proposal.Candidate]; ok && candidateNeedPD {
352 delete(s.Candidates, proposal.Candidate)
354 case proposalTypeMinerRewardDistributionModify:
355 minerRewardPerThousand = s.Proposals[hashKey].MinerRewardPerThousand
362 func (s *Snapshot) updateSnapshotByProposals(proposals []Proposal, headerHeight uint64) {
363 for _, proposal := range proposals {
364 proposal.ReceivedNumber = headerHeight
365 s.Proposals[proposal.Hash] = &proposal
369 func (s *Snapshot) updateSnapshotForExpired() {
370 // deal the expired vote
371 var expiredVotes []*Vote
372 for voterAddress, voteNumber := range s.Voters {
373 if s.Number-voteNumber > s.config.Epoch {
375 if expiredVote, ok := s.Votes[voterAddress]; ok {
376 expiredVotes = append(expiredVotes, expiredVote)
380 // remove expiredVotes only enough voters left
381 if uint64(len(s.Voters)-len(expiredVotes)) >= s.config.MaxSignerCount {
382 for _, expiredVote := range expiredVotes {
383 s.Tally[expiredVote.Candidate] -= expiredVote.Stake
385 if s.Tally[expiredVote.Candidate] == 0 {
386 delete(s.Tally, expiredVote.Candidate)
388 delete(s.Votes, expiredVote.Voter)
389 delete(s.Voters, expiredVote.Voter)
393 // deal the expired confirmation
394 for blockNumber := range s.Confirmations {
395 if s.Number-blockNumber > s.config.MaxSignerCount {
396 delete(s.Confirmations, blockNumber)
401 // remove 0 stake tally
403 for address, tally := range s.Tally {
404 if tally <= 0 && uint64(len(s.Tally)) > s.config.MaxSignerCount {
405 delete(s.Tally, address)
410 func (s *Snapshot) updateSnapshotByConfirmations(confirmations []Confirmation) {
411 for _, confirmation := range confirmations {
412 _, ok := s.Confirmations[confirmation.BlockNumber]
414 s.Confirmations[confirmation.BlockNumber] = []string{}
416 addConfirmation := true
417 for _, address := range s.Confirmations[confirmation.BlockNumber] {
418 if confirmation.Signer == address {
419 addConfirmation = false
423 if addConfirmation == true {
424 s.Confirmations[confirmation.BlockNumber] = append(s.Confirmations[confirmation.BlockNumber], confirmation.Signer)
429 func (s *Snapshot) updateSnapshotByVotes(votes []Vote, headerHeight uint64) {
430 for _, vote := range votes {
431 // update Votes, Tally, Voters data
432 if lastVote, ok := s.Votes[vote.Voter]; ok {
433 s.Tally[lastVote.Candidate] = s.Tally[lastVote.Candidate] - lastVote.Stake
435 if _, ok := s.Tally[vote.Candidate]; ok {
436 s.Tally[vote.Candidate] = s.Tally[vote.Candidate] + vote.Stake
438 s.Tally[vote.Candidate] = vote.Stake
439 if !candidateNeedPD {
440 s.Candidates[vote.Candidate] = candidateStateNormal
443 s.Votes[vote.Voter] = &Vote{vote.Voter, vote.Candidate, vote.Stake}
444 s.Voters[vote.Voter] = headerHeight
448 func (s *Snapshot) updateSnapshotByMPVotes(votes []Vote) {
449 for _, txVote := range votes {
450 if lastVote, ok := s.Votes[txVote.Voter]; ok {
451 s.Tally[lastVote.Candidate] = s.Tally[lastVote.Candidate] - lastVote.Stake
452 s.Tally[lastVote.Candidate] = s.Tally[lastVote.Candidate] + txVote.Stake
453 s.Votes[txVote.Voter] = &Vote{Voter: txVote.Voter, Candidate: lastVote.Candidate, Stake: txVote.Stake}
458 func (s *Snapshot) updateSnapshotForPunish(signerMissing []string, headerNumber uint64, coinbase string) {
459 // punish the missing signer
460 for _, signerMissing := range signerMissing {
461 if _, ok := s.Punished[signerMissing]; ok {
462 s.Punished[signerMissing] += missingPublishCredit
464 s.Punished[signerMissing] = missingPublishCredit
467 // reduce the punish of sign signer
468 if _, ok := s.Punished[coinbase]; ok {
470 if s.Punished[coinbase] > signRewardCredit {
471 s.Punished[coinbase] -= signRewardCredit
473 delete(s.Punished, coinbase)
476 // reduce the punish for all punished
477 for signerEach := range s.Punished {
478 if s.Punished[signerEach] > autoRewardCredit {
479 s.Punished[signerEach] -= autoRewardCredit
481 delete(s.Punished, signerEach)
486 // inturn returns if a signer at a given block height is in-turn or not.
487 func (s *Snapshot) inturn(signer string, headerTime uint64) bool {
488 // if all node stop more than period of one loop
489 loopIndex := int((headerTime-s.LoopStartTime)/s.config.Period) % len(s.Signers)
490 if loopIndex >= len(s.Signers) {
492 } else if *s.Signers[loopIndex] != signer {
499 // check if address belong to voter
500 func (s *Snapshot) isVoter(address string) bool {
501 if _, ok := s.Voters[address]; ok {
507 // check if address belong to candidate
508 func (s *Snapshot) isCandidate(address string) bool {
509 if _, ok := s.Candidates[address]; ok {
515 // get last block number meet the confirm condition
516 func (s *Snapshot) getLastConfirmedBlockNumber(confirmations []Confirmation) *big.Int {
517 cpyConfirmations := make(map[uint64][]string)
518 for blockNumber, confirmers := range s.Confirmations {
519 cpyConfirmations[blockNumber] = make([]string, len(confirmers))
520 copy(cpyConfirmations[blockNumber], confirmers)
522 // update confirmation into snapshot
523 for _, confirmation := range confirmations {
524 _, ok := cpyConfirmations[confirmation.BlockNumber]
526 cpyConfirmations[confirmation.BlockNumber] = []string{}
528 addConfirmation := true
529 for _, address := range cpyConfirmations[confirmation.BlockNumber] {
530 if confirmation.Signer == address {
531 addConfirmation = false
535 if addConfirmation == true {
536 cpyConfirmations[confirmation.BlockNumber] = append(cpyConfirmations[confirmation.BlockNumber], confirmation.Signer)
541 for ; i > s.Number-s.config.MaxSignerCount*2/3+1; i-- {
542 if confirmers, ok := cpyConfirmations[i]; ok {
543 if len(confirmers) > int(s.config.MaxSignerCount*2/3) {
544 return big.NewInt(int64(i))
548 return big.NewInt(int64(i))