8 log "github.com/sirupsen/logrus"
9 "github.com/vapor/chain"
10 "github.com/vapor/protocol/bc"
11 "github.com/vapor/protocol/bc/types"
16 * vapor:version:category:action/data
20 vaporCategoryEvent = "event"
21 vaporCategoryLog = "oplog"
22 vaporCategorySC = "sc"
23 vaporEventVote = "vote"
24 vaporEventConfirm = "confirm"
25 vaporEventPorposal = "proposal"
26 vaporEventDeclare = "declare"
36 posEventConfirmNumber = 4
41 proposalTypeCandidateAdd = 1
42 proposalTypeCandidateRemove = 2
43 proposalTypeMinerRewardDistributionModify = 3 // count in one thousand
48 maxValidationLoopCnt = 123500 // About one month if seal each block per second & 21 super nodes
49 minValidationLoopCnt = 12350 // About three days if seal each block per second & 21 super nodes
50 defaultValidationLoopCnt = 30875 // About one week if seal each block per second & 21 super nodes
54 // vote come from custom tx which data like "vapor:1:event:vote"
55 // Sender of tx is Voter, the tx.to is Candidate
56 // Stake is the balance of Voter when create this vote
58 Voter string `json:"Voter"`
59 Candidate string `json:"Candidate"`
60 Stake uint64 `json:"Stake"`
64 // confirmation come from custom tx which data like "vapor:1:event:confirm:123"
65 // 123 is the block number be confirmed
66 // Sender of tx is Signer only if the signer in the SignerQueue for block number 123
67 type Confirmation struct {
68 Signer string `json:"signer"`
69 BlockNumber uint64 `json:"block_number"`
73 // proposal come from custom tx which data like "vapor:1:event:proposal:candidate:add:address" or "vapor:1:event:proposal:percentage:60"
74 // proposal only come from the current candidates
75 // not only candidate add/remove , current signer can proposal for params modify like percentage of reward distribution ...
76 type Proposal struct {
77 Hash bc.Hash `json:"hash"` // tx hash
78 ValidationLoopCnt uint64 `json:"ValidationLoopCnt"` // validation block number length of this proposal from the received block number
79 ImplementNumber uint64 `json:"ImplementNumber"` // block number to implement modification in this proposal
80 ProposalType uint64 `json:"ProposalType"` // type of proposal 1 - add candidate 2 - remove candidate ...
81 Proposer string `json:"Proposer"` //
82 Candidate string `json:"Candidate"`
83 MinerRewardPerThousand uint64 `json:"MinerRewardPerThousand"`
84 Declares []*Declare `json:"Declares"` // Declare this proposal received
85 ReceivedNumber uint64 `json:"ReceivedNumber"` // block number of proposal received
88 func (p *Proposal) copy() *Proposal {
91 ValidationLoopCnt: p.ValidationLoopCnt,
92 ImplementNumber: p.ImplementNumber,
93 ProposalType: p.ProposalType,
95 Candidate: p.Candidate,
96 MinerRewardPerThousand: p.MinerRewardPerThousand,
97 Declares: make([]*Declare, len(p.Declares)),
98 ReceivedNumber: p.ReceivedNumber,
101 copy(cpy.Declares, p.Declares)
106 // declare come from custom tx which data like "vapor:1:event:declare:hash:yes"
107 // proposal only come from the current candidates
108 // hash is the hash of proposal tx
109 type Declare struct {
110 ProposalHash bc.Hash `json:"ProposalHash"`
111 Declarer string `json:"Declarer"`
112 Decision bool `json:"Decision"`
115 // HeaderExtra is the struct of info in header.Extra[extraVanity:len(header.extra)-extraSeal]
116 type HeaderExtra struct {
117 CurrentBlockConfirmations []Confirmation `json:"current_block_confirmations"`
118 CurrentBlockVotes []Vote `json:"CurrentBlockVotes"`
119 CurrentBlockProposals []Proposal `json:"CurrentBlockProposals"`
120 CurrentBlockDeclares []Declare `json:"CurrentBlockDeclares"`
121 ModifyPredecessorVotes []Vote `json:"ModifyPredecessorVotes"`
122 LoopStartTime uint64 `json:"LoopStartTime"`
123 SignerQueue []string `json:"SignerQueue"`
124 SignerMissing []string `json:"SignerMissing"`
125 ConfirmedBlockNumber uint64 `json:"ConfirmedBlockNumber"`
128 // Calculate Votes from transaction in this block, write into header.Extra
129 func (d *Dpos) processCustomTx(headerExtra HeaderExtra, c chain.Chain, header *types.BlockHeader, txs []*bc.Tx) (HeaderExtra, error) {
136 height = header.Height
138 snap, err = d.snapshot(c, height-1, header.PreviousBlockHash, nil, nil, defaultLoopCntRecalculateSigners)
140 return headerExtra, err
144 for _, tx := range txs {
151 for _, value := range tx.Entries {
152 switch d := value.(type) {
162 if len(dpos.Data) >= len(vaporPrefix) {
164 txDataInfo := strings.Split(txData, ":")
165 if len(txDataInfo) >= vaporMinSplitLen && txDataInfo[posPrefix] == vaporPrefix && txDataInfo[posVersion] == vaporVersion {
166 switch txDataInfo[posCategory] {
167 case vaporCategoryEvent:
168 if len(txDataInfo) > vaporMinSplitLen {
169 if txDataInfo[posEventVote] == vaporEventVote && (!candidateNeedPD || snap.isCandidate(to)) {
170 headerExtra.CurrentBlockVotes = d.processEventVote(headerExtra.CurrentBlockVotes, stake, from, to)
171 } else if txDataInfo[posEventConfirm] == vaporEventConfirm {
172 headerExtra.CurrentBlockConfirmations = d.processEventConfirm(headerExtra.CurrentBlockConfirmations, c, txDataInfo, height, tx, from)
173 } else if txDataInfo[posEventProposal] == vaporEventPorposal && snap.isCandidate(from) {
174 headerExtra.CurrentBlockProposals = d.processEventProposal(headerExtra.CurrentBlockProposals, txDataInfo, tx, from)
175 } else if txDataInfo[posEventDeclare] == vaporEventDeclare && snap.isCandidate(from) {
176 headerExtra.CurrentBlockDeclares = d.processEventDeclare(headerExtra.CurrentBlockDeclares, txDataInfo, tx, from)
180 // todo : something wrong, leave this transaction to process as normal transaction
183 case vaporCategoryLog:
185 case vaporCategorySC:
192 headerExtra.ModifyPredecessorVotes = d.processPredecessorVoter(headerExtra.ModifyPredecessorVotes, stake, from, to, snap)
198 return headerExtra, nil
201 func (d *Dpos) processEventProposal(currentBlockProposals []Proposal, txDataInfo []string, tx *bc.Tx, proposer string) []Proposal {
202 proposal := Proposal{
204 ValidationLoopCnt: defaultValidationLoopCnt,
205 ImplementNumber: uint64(1),
206 ProposalType: proposalTypeCandidateAdd,
208 MinerRewardPerThousand: minerRewardPerThousand,
209 Declares: []*Declare{},
210 ReceivedNumber: uint64(0),
213 for i := 0; i < len(txDataInfo[posEventProposal+1:])/2; i++ {
214 k, v := txDataInfo[posEventProposal+1+i*2], txDataInfo[posEventProposal+2+i*2]
217 // If vlcnt is missing then user default value, but if the vlcnt is beyond the min/max value then ignore this proposal
218 if validationLoopCnt, err := strconv.Atoi(v); err != nil || validationLoopCnt < minValidationLoopCnt || validationLoopCnt > maxValidationLoopCnt {
219 return currentBlockProposals
221 proposal.ValidationLoopCnt = uint64(validationLoopCnt)
223 case "implement_number":
224 if implementNumber, err := strconv.Atoi(v); err != nil || implementNumber <= 0 {
225 return currentBlockProposals
227 proposal.ImplementNumber = uint64(implementNumber)
229 case "proposal_type":
230 if proposalType, err := strconv.Atoi(v); err != nil || (proposalType != proposalTypeCandidateAdd && proposalType != proposalTypeCandidateRemove && proposalType != proposalTypeMinerRewardDistributionModify) {
231 return currentBlockProposals
233 proposal.ProposalType = uint64(proposalType)
237 //proposal.Candidate.UnmarshalText([]byte(v))
239 address, err := common.DecodeAddress(v, &consensus.ActiveNetParams)
241 return currentBlockProposals
244 proposal.Candidate = v
246 // miner reward per thousand
247 if mrpt, err := strconv.Atoi(v); err != nil || mrpt < 0 || mrpt > 1000 {
248 return currentBlockProposals
250 proposal.MinerRewardPerThousand = uint64(mrpt)
256 return append(currentBlockProposals, proposal)
259 func (d *Dpos) processEventDeclare(currentBlockDeclares []Declare, txDataInfo []string, tx *bc.Tx, declarer string) []Declare {
261 ProposalHash: bc.Hash{},
266 for i := 0; i < len(txDataInfo[posEventDeclare+1:])/2; i++ {
267 k, v := txDataInfo[posEventDeclare+1+i*2], txDataInfo[posEventDeclare+2+i*2]
270 declare.ProposalHash.UnmarshalText([]byte(v))
273 declare.Decision = true
274 } else if v == "no" {
275 declare.Decision = false
277 return currentBlockDeclares
282 return append(currentBlockDeclares, declare)
285 func (d *Dpos) processEventVote(currentBlockVotes []Vote, stake uint64, voter, to string) []Vote {
287 //if new(big.Int).SetUint64(stake).Cmp(d.config.MinVoterBalance) > 0 {
288 currentBlockVotes = append(currentBlockVotes, Vote{
294 return currentBlockVotes
297 func (d *Dpos) processEventConfirm(currentBlockConfirmations []Confirmation, c chain.Chain, txDataInfo []string, number uint64, tx *bc.Tx, confirmer string) []Confirmation {
298 if len(txDataInfo) > posEventConfirmNumber {
299 confirmedBlockNumber, err := strconv.Atoi(txDataInfo[posEventConfirmNumber])
300 if err != nil || number-uint64(confirmedBlockNumber) > d.config.MaxSignerCount || number-uint64(confirmedBlockNumber) < 0 {
301 return currentBlockConfirmations
303 confirmedHeader, err := c.GetBlockByHeight(uint64(confirmedBlockNumber))
304 if confirmedHeader == nil {
305 log.Info("Fail to get confirmedHeader")
306 return currentBlockConfirmations
308 confirmedHeaderExtra := HeaderExtra{}
309 if extraVanity+extraSeal > len(confirmedHeader.Extra) {
310 return currentBlockConfirmations
312 //err = rlp.DecodeBytes(confirmedHeader.Extra[extraVanity:len(confirmedHeader.Extra)-extraSeal], &confirmedHeaderExtra)
313 if err := json.Unmarshal(confirmedHeader.Extra[extraVanity:len(confirmedHeader.Extra)-extraSeal], &confirmedHeaderExtra); err != nil {
314 log.Info("Fail to decode parent header", "err", err)
315 return currentBlockConfirmations
317 for _, s := range confirmedHeaderExtra.SignerQueue {
319 currentBlockConfirmations = append(currentBlockConfirmations, Confirmation{
321 BlockNumber: uint64(confirmedBlockNumber),
328 return currentBlockConfirmations
331 func (d *Dpos) processPredecessorVoter(modifyPredecessorVotes []Vote, stake uint64, voter, to string, snap *Snapshot) []Vote {
333 if snap.isVoter(voter) {
334 modifyPredecessorVotes = append(modifyPredecessorVotes, Vote{
340 if snap.isVoter(to) {
341 modifyPredecessorVotes = append(modifyPredecessorVotes, Vote{
349 return modifyPredecessorVotes