10 lru "github.com/hashicorp/golang-lru"
11 "github.com/vapor/common"
12 "github.com/vapor/config"
13 "github.com/vapor/consensus"
14 "github.com/vapor/crypto"
15 "github.com/vapor/crypto/ed25519/chainkd"
16 "github.com/vapor/protocol"
17 "github.com/vapor/protocol/bc"
18 "github.com/vapor/protocol/bc/types"
22 defaultFullCredit = 1000 // no punished
23 missingPublishCredit = 100 // punished for missing one block seal
24 signRewardCredit = 10 // seal one block
25 autoRewardCredit = 1 // credit auto recover for each block
26 minCalSignerQueueCredit = 300 // when calculate the signerQueue
27 defaultOfficialMaxSignerCount = 21 // official max signer count
28 defaultOfficialFirstLevelCount = 10 // official first level , 100% in signer queue
29 defaultOfficialSecondLevelCount = 20 // official second level, 60% in signer queue
30 defaultOfficialThirdLevelCount = 30 // official third level, 40% in signer queue
31 // the credit of one signer is at least minCalSignerQueueCredit
32 candidateStateNormal = 1
33 candidateMaxLen = 500 // if candidateNeedPD is false and candidate is more than candidateMaxLen, then minimum tickets candidates will be remove in each LCRS*loop
36 var errIncorrectTallyCount = errors.New("incorrect tally count")
38 type Snapshot struct {
39 config *config.DposConfig // Consensus engine configuration parameters
40 sigcache *lru.ARCCache // Cache of recent block signatures to speed up ecrecover
41 LCRS uint64 // Loop count to recreate signers from top tally
42 Period uint64 `json:"period"` // Period of seal each block
43 Number uint64 `json:"number"` // Block Number where the snapshot was created
44 ConfirmedNumber uint64 `json:"confirmed_number"` // Block Number confirmed when the snapshot was created
45 Hash bc.Hash `json:"hash"` // Block hash where the snapshot was created
46 HistoryHash []bc.Hash `json:"historyHash"` // Block hash list for two recent loop
47 Signers []*string `json:"signers"` // Signers queue in current header
48 Votes map[string]*Vote `json:"votes"` // All validate votes from genesis block
49 Tally map[string]uint64 `json:"tally"` // Stake for each candidate address
50 Voters map[string]uint64 `json:"voters"` // Block height for each voter address
51 Candidates map[string]uint64 `json:"candidates"` // Candidates for Signers (0- adding procedure 1- normal 2- removing procedure)
52 Punished map[string]uint64 `json:"punished"` // The signer be punished count cause of missing seal
53 Confirmations map[uint64][]string `json:"confirms"` // The signer confirm given block height
54 Proposals map[bc.Hash]*Proposal `json:"proposals"` // The Proposals going or success (failed proposal will be removed)
55 HeaderTime uint64 `json:"headerTime"` // Time of the current header
56 LoopStartTime uint64 `json:"loopStartTime"` // Start Time of the current loop
59 // newSnapshot creates a new snapshot with the specified startup parameters. only ever use if for
61 func newSnapshot(config *config.DposConfig, sigcache *lru.ARCCache, hash bc.Hash, votes []*Vote, lcrs uint64) *Snapshot {
67 Period: config.Period,
71 HistoryHash: []bc.Hash{},
73 Votes: make(map[string]*Vote),
74 Tally: make(map[string]uint64),
75 Voters: make(map[string]uint64),
76 Punished: make(map[string]uint64),
77 Candidates: make(map[string]uint64),
78 Confirmations: make(map[uint64][]string),
79 Proposals: make(map[bc.Hash]*Proposal),
80 HeaderTime: uint64(time.Now().Unix()) - 1,
81 LoopStartTime: config.GenesisTimestamp,
83 snap.HistoryHash = append(snap.HistoryHash, hash)
84 for _, vote := range votes {
85 // init Votes from each vote
86 snap.Votes[vote.Voter] = vote
88 _, ok := snap.Tally[vote.Candidate]
90 snap.Tally[vote.Candidate] = 0
92 snap.Tally[vote.Candidate] += vote.Stake
94 snap.Voters[vote.Voter] = 0 // block height is 0 , vote in genesis block
96 snap.Candidates[vote.Voter] = candidateStateNormal
99 for i := 0; i < int(config.MaxSignerCount); i++ {
100 snap.Signers = append(snap.Signers, &config.SelfVoteSigners[i%len(config.SelfVoteSigners)])
106 // loadSnapshot loads an existing snapshot from the database.
107 func loadSnapshot(config *config.DposConfig, sigcache *lru.ARCCache, store protocol.Store, hash bc.Hash) (*Snapshot, error) {
108 data, err := store.Get(&hash)
112 snap := new(Snapshot)
113 if err := json.Unmarshal(data, snap); err != nil {
117 snap.sigcache = sigcache
121 // store inserts the snapshot into the database.
122 func (s *Snapshot) store(store protocol.Store) error {
123 data, err := json.Marshal(s)
127 return store.Set(&s.Hash, data)
130 // copy creates a deep copy of the snapshot, though not the individual votes.
131 func (s *Snapshot) copy() *Snapshot {
134 sigcache: s.sigcache,
138 ConfirmedNumber: s.ConfirmedNumber,
140 HistoryHash: make([]bc.Hash, len(s.HistoryHash)),
142 Signers: make([]*string, len(s.Signers)),
143 Votes: make(map[string]*Vote),
144 Tally: make(map[string]uint64),
145 Voters: make(map[string]uint64),
146 Candidates: make(map[string]uint64),
147 Punished: make(map[string]uint64),
148 Proposals: make(map[bc.Hash]*Proposal),
149 Confirmations: make(map[uint64][]string),
151 HeaderTime: s.HeaderTime,
152 LoopStartTime: s.LoopStartTime,
154 copy(cpy.HistoryHash, s.HistoryHash)
155 copy(cpy.Signers, s.Signers)
156 for voter, vote := range s.Votes {
157 cpy.Votes[voter] = &Vote{
159 Candidate: vote.Candidate,
163 for candidate, tally := range s.Tally {
164 cpy.Tally[candidate] = tally
166 for voter, number := range s.Voters {
167 cpy.Voters[voter] = number
169 for candidate, state := range s.Candidates {
170 cpy.Candidates[candidate] = state
172 for signer, cnt := range s.Punished {
173 cpy.Punished[signer] = cnt
175 for blockNumber, confirmers := range s.Confirmations {
176 cpy.Confirmations[blockNumber] = make([]string, len(confirmers))
177 copy(cpy.Confirmations[blockNumber], confirmers)
179 for txHash, proposal := range s.Proposals {
180 cpy.Proposals[txHash] = proposal.copy()
186 // apply creates a new authorization snapshot by applying the given headers to
188 func (s *Snapshot) apply(headers []*types.BlockHeader) (*Snapshot, error) {
189 // Allow passing in no headers for cleaner code
190 if len(headers) == 0 {
193 // Sanity check that the headers can be applied
194 for i := 0; i < len(headers)-1; i++ {
195 if headers[i+1].Height != headers[i].Height+1 {
196 return nil, errInvalidVotingChain
199 if headers[0].Height != s.Number+1 {
200 return nil, errInvalidVotingChain
202 // Iterate through the headers and create a new snapshot
204 for _, header := range headers {
206 // Resolve the authorization key and check against signers
207 coinbase, err := ecrecover(header, s.sigcache, nil)
212 xpub := &chainkd.XPub{}
213 xpub.UnmarshalText(header.Coinbase)
214 derivedPK := xpub.PublicKey()
215 pubHash := crypto.Ripemd160(derivedPK)
216 address, err := common.NewAddressWitnessPubKeyHash(pubHash, &consensus.ActiveNetParams)
220 if coinbase != address.EncodeAddress() {
221 return nil, errUnauthorized
224 headerExtra := HeaderExtra{}
225 if err := json.Unmarshal(header.Extra[extraVanity:len(header.Extra)-extraSeal], &headerExtra); err != nil {
229 snap.HeaderTime = header.Timestamp
230 snap.LoopStartTime = headerExtra.LoopStartTime
232 for i := range headerExtra.SignerQueue {
233 snap.Signers = append(snap.Signers, &headerExtra.SignerQueue[i])
236 snap.ConfirmedNumber = headerExtra.ConfirmedBlockNumber
238 if len(snap.HistoryHash) >= int(s.config.MaxSignerCount)*2 {
239 snap.HistoryHash = snap.HistoryHash[1 : int(s.config.MaxSignerCount)*2]
242 snap.HistoryHash = append(snap.HistoryHash, header.Hash())
244 // deal the new confirmation in this block
245 snap.updateSnapshotByConfirmations(headerExtra.CurrentBlockConfirmations)
247 // deal the new vote from voter
248 snap.updateSnapshotByVotes(headerExtra.CurrentBlockVotes, header.Height)
250 // deal the voter which balance modified
251 //snap.updateSnapshotByMPVotes(headerExtra.ModifyPredecessorVotes)
253 // deal the snap related with punished
254 //snap.updateSnapshotForPunish(headerExtra.SignerMissing, header.Height, header.Coinbase)
257 snap.updateSnapshotByProposals(headerExtra.CurrentBlockProposals, header.Height)
260 snap.updateSnapshotByDeclares(headerExtra.CurrentBlockDeclares, header.Height)
262 // calculate proposal result
263 snap.calculateProposalResult(header.Height)
265 // check the len of candidate if not candidateNeedPD
266 if !candidateNeedPD && (snap.Number+1)%(snap.config.MaxSignerCount*snap.LCRS) == 0 && len(snap.Candidates) > candidateMaxLen {
267 snap.removeExtraCandidate()
271 snap.Number += uint64(len(headers))
272 snap.Hash = headers[len(headers)-1].Hash()
273 snap.updateSnapshotForExpired()
274 err := snap.verifyTallyCnt()
281 func (s *Snapshot) removeExtraCandidate() {
282 // remove minimum tickets tally beyond candidateMaxLen
283 tallySlice := s.buildTallySlice()
284 sort.Sort(TallySlice(tallySlice))
285 if len(tallySlice) > candidateMaxLen {
286 removeNeedTally := tallySlice[candidateMaxLen:]
287 for _, tallySlice := range removeNeedTally {
288 delete(s.Candidates, tallySlice.addr)
293 func (s *Snapshot) verifyTallyCnt() error {
295 tallyTarget := make(map[string]uint64)
296 for _, v := range s.Votes {
297 if _, ok := tallyTarget[v.Candidate]; ok {
298 tallyTarget[v.Candidate] = tallyTarget[v.Candidate] + v.Stake
300 tallyTarget[v.Candidate] = v.Stake
303 for address, tally := range s.Tally {
304 if targetTally, ok := tallyTarget[address]; ok && targetTally == tally {
312 func (s *Snapshot) updateSnapshotByDeclares(declares []Declare, headerHeight uint64) {
313 for _, declare := range declares {
314 if proposal, ok := s.Proposals[declare.ProposalHash]; ok {
315 // check the proposal enable status and valid block number
316 if proposal.ReceivedNumber+proposal.ValidationLoopCnt*s.config.MaxSignerCount < headerHeight || !s.isCandidate(declare.Declarer) {
319 // check if this signer already declare on this proposal
320 alreadyDeclare := false
321 for _, v := range proposal.Declares {
322 if v.Declarer == declare.Declarer {
323 // this declarer already declare for this proposal
324 alreadyDeclare = true
331 // add declare to proposal
332 s.Proposals[declare.ProposalHash].Declares = append(s.Proposals[declare.ProposalHash].Declares,
333 &Declare{declare.ProposalHash, declare.Declarer, declare.Decision})
339 func (s *Snapshot) calculateProposalResult(headerHeight uint64) {
341 for hashKey, proposal := range s.Proposals {
342 // the result will be calculate at receiverdNumber + vlcnt + 1
343 if proposal.ReceivedNumber+proposal.ValidationLoopCnt*s.config.MaxSignerCount+1 == headerHeight {
344 // calculate the current stake of this proposal
345 judegmentStake := big.NewInt(0)
346 for _, tally := range s.Tally {
347 judegmentStake.Add(judegmentStake, new(big.Int).SetUint64(tally))
349 judegmentStake.Mul(judegmentStake, big.NewInt(2))
350 judegmentStake.Div(judegmentStake, big.NewInt(3))
351 // calculate declare stake
352 yesDeclareStake := big.NewInt(0)
353 for _, declare := range proposal.Declares {
354 if declare.Decision {
355 if _, ok := s.Tally[declare.Declarer]; ok {
356 yesDeclareStake.Add(yesDeclareStake, new(big.Int).SetUint64(s.Tally[declare.Declarer]))
360 if yesDeclareStake.Cmp(judegmentStake) > 0 {
361 // process add candidate
362 switch proposal.ProposalType {
363 case proposalTypeCandidateAdd:
365 s.Candidates[s.Proposals[hashKey].Candidate] = candidateStateNormal
367 case proposalTypeCandidateRemove:
368 if _, ok := s.Candidates[proposal.Candidate]; ok && candidateNeedPD {
369 delete(s.Candidates, proposal.Candidate)
371 case proposalTypeMinerRewardDistributionModify:
372 minerRewardPerThousand = s.Proposals[hashKey].MinerRewardPerThousand
383 func (s *Snapshot) updateSnapshotByProposals(proposals []Proposal, headerHeight uint64) {
384 for _, proposal := range proposals {
385 proposal.ReceivedNumber = headerHeight
386 s.Proposals[proposal.Hash] = &proposal
390 func (s *Snapshot) updateSnapshotForExpired() {
392 // deal the expired vote
393 var expiredVotes []*Vote
394 for voterAddress, voteNumber := range s.Voters {
395 if s.Number-voteNumber > s.config.Epoch {
397 if expiredVote, ok := s.Votes[voterAddress]; ok {
398 expiredVotes = append(expiredVotes, expiredVote)
402 // remove expiredVotes only enough voters left
403 if uint64(len(s.Voters)-len(expiredVotes)) >= s.config.MaxSignerCount {
404 for _, expiredVote := range expiredVotes {
405 s.Tally[expiredVote.Candidate] -= expiredVote.Stake
408 if s.Tally[expiredVote.Candidate] == 0 {
409 delete(s.Tally, expiredVote.Candidate)
411 delete(s.Votes, expiredVote.Voter)
412 delete(s.Voters, expiredVote.Voter)
417 // deal the expired confirmation
418 for blockNumber := range s.Confirmations {
419 if s.Number-blockNumber > s.config.MaxSignerCount {
420 delete(s.Confirmations, blockNumber)
426 // remove 0 stake tally
427 for address, tally := range s.Tally {
429 delete(s.Tally, address)
435 func (s *Snapshot) updateSnapshotByConfirmations(confirmations []Confirmation) {
436 for _, confirmation := range confirmations {
437 _, ok := s.Confirmations[confirmation.BlockNumber]
439 s.Confirmations[confirmation.BlockNumber] = []string{}
441 addConfirmation := true
442 for _, address := range s.Confirmations[confirmation.BlockNumber] {
443 if confirmation.Signer == address {
444 addConfirmation = false
448 if addConfirmation == true {
449 s.Confirmations[confirmation.BlockNumber] = append(s.Confirmations[confirmation.BlockNumber], confirmation.Signer)
454 func (s *Snapshot) updateSnapshotByVotes(votes []Vote, headerHeight uint64) {
455 for _, vote := range votes {
456 // update Votes, Tally, Voters data
457 if lastVote, ok := s.Votes[vote.Voter]; ok {
458 s.Tally[lastVote.Candidate] = s.Tally[lastVote.Candidate] - lastVote.Stake
460 if _, ok := s.Tally[vote.Candidate]; ok {
461 s.Tally[vote.Candidate] = s.Tally[vote.Candidate] + vote.Stake
463 s.Tally[vote.Candidate] = vote.Stake
464 if !candidateNeedPD {
465 s.Candidates[vote.Candidate] = candidateStateNormal
468 s.Votes[vote.Voter] = &Vote{vote.Voter, vote.Candidate, vote.Stake}
469 s.Voters[vote.Voter] = headerHeight
473 func (s *Snapshot) updateSnapshotByMPVotes(votes []Vote) {
474 for _, txVote := range votes {
475 if lastVote, ok := s.Votes[txVote.Voter]; ok {
476 s.Tally[lastVote.Candidate] = s.Tally[lastVote.Candidate] - lastVote.Stake
477 s.Tally[lastVote.Candidate] = s.Tally[lastVote.Candidate] + txVote.Stake
478 s.Votes[txVote.Voter] = &Vote{Voter: txVote.Voter, Candidate: lastVote.Candidate, Stake: txVote.Stake}
483 func (s *Snapshot) updateSnapshotForPunish(signerMissing []string, headerNumber uint64, coinbase string) {
484 // set punished count to half of origin in Epoch
486 if headerNumber.Uint64()%s.config.Epoch == 0 {
487 for bePublished := range s.Punished {
488 if count := s.Punished[bePublished] / 2; count > 0 {
489 s.Punished[bePublished] = count
491 delete(s.Punished, bePublished)
496 // punish the missing signer
497 for _, signerMissing := range signerMissing {
498 if _, ok := s.Punished[signerMissing]; ok {
499 s.Punished[signerMissing] += missingPublishCredit
501 s.Punished[signerMissing] = missingPublishCredit
504 // reduce the punish of sign signer
505 if _, ok := s.Punished[coinbase]; ok {
507 if s.Punished[coinbase] > signRewardCredit {
508 s.Punished[coinbase] -= signRewardCredit
510 delete(s.Punished, coinbase)
513 // reduce the punish for all punished
514 for signerEach := range s.Punished {
515 if s.Punished[signerEach] > autoRewardCredit {
516 s.Punished[signerEach] -= autoRewardCredit
518 delete(s.Punished, signerEach)
523 // inturn returns if a signer at a given block height is in-turn or not.
524 func (s *Snapshot) inturn(signer string, headerTime uint64) bool {
525 // if all node stop more than period of one loop
526 loopIndex := int((headerTime-s.LoopStartTime)/s.config.Period) % len(s.Signers)
527 if loopIndex >= len(s.Signers) {
529 } else if *s.Signers[loopIndex] != signer {
536 // check if address belong to voter
537 func (s *Snapshot) isVoter(address string) bool {
538 if _, ok := s.Voters[address]; ok {
544 // check if address belong to candidate
545 func (s *Snapshot) isCandidate(address string) bool {
546 if _, ok := s.Candidates[address]; ok {
552 // get last block number meet the confirm condition
553 func (s *Snapshot) getLastConfirmedBlockNumber(confirmations []Confirmation) *big.Int {
555 cpyConfirmations := make(map[uint64][]string)
556 for blockNumber, confirmers := range s.Confirmations {
557 cpyConfirmations[blockNumber] = make([]string, len(confirmers))
558 copy(cpyConfirmations[blockNumber], confirmers)
560 // update confirmation into snapshot
561 for _, confirmation := range confirmations {
562 _, ok := cpyConfirmations[confirmation.BlockNumber]
564 cpyConfirmations[confirmation.BlockNumber] = []string{}
566 addConfirmation := true
567 for _, address := range cpyConfirmations[confirmation.BlockNumber] {
568 if confirmation.Signer == address {
569 addConfirmation = false
573 if addConfirmation == true {
575 cpyConfirmations[confirmation.BlockNumber] = append(cpyConfirmations[confirmation.BlockNumber], confirmation.Signer)
580 for ; i > s.Number-s.config.MaxSignerCount*2/3+1; i-- {
581 if confirmers, ok := cpyConfirmations[i]; ok {
582 if len(confirmers) > int(s.config.MaxSignerCount*2/3) {
583 return big.NewInt(int64(i))
587 return big.NewInt(int64(i))