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:
193 return headerExtra, nil
196 func (d *Dpos) processEventProposal(currentBlockProposals []Proposal, txDataInfo []string, tx *bc.Tx, proposer string) []Proposal {
197 proposal := Proposal{
199 ValidationLoopCnt: defaultValidationLoopCnt,
200 ImplementNumber: uint64(1),
201 ProposalType: proposalTypeCandidateAdd,
203 MinerRewardPerThousand: minerRewardPerThousand,
204 Declares: []*Declare{},
205 ReceivedNumber: uint64(0),
208 for i := 0; i < len(txDataInfo[posEventProposal+1:])/2; i++ {
209 k, v := txDataInfo[posEventProposal+1+i*2], txDataInfo[posEventProposal+2+i*2]
212 // If vlcnt is missing then user default value, but if the vlcnt is beyond the min/max value then ignore this proposal
213 if validationLoopCnt, err := strconv.Atoi(v); err != nil || validationLoopCnt < minValidationLoopCnt || validationLoopCnt > maxValidationLoopCnt {
214 return currentBlockProposals
216 proposal.ValidationLoopCnt = uint64(validationLoopCnt)
218 case "implement_number":
219 if implementNumber, err := strconv.Atoi(v); err != nil || implementNumber <= 0 {
220 return currentBlockProposals
222 proposal.ImplementNumber = uint64(implementNumber)
224 case "proposal_type":
225 if proposalType, err := strconv.Atoi(v); err != nil || (proposalType != proposalTypeCandidateAdd && proposalType != proposalTypeCandidateRemove && proposalType != proposalTypeMinerRewardDistributionModify) {
226 return currentBlockProposals
228 proposal.ProposalType = uint64(proposalType)
232 //proposal.Candidate.UnmarshalText([]byte(v))
234 address, err := common.DecodeAddress(v, &consensus.ActiveNetParams)
236 return currentBlockProposals
239 proposal.Candidate = v
241 // miner reward per thousand
242 if mrpt, err := strconv.Atoi(v); err != nil || mrpt < 0 || mrpt > 1000 {
243 return currentBlockProposals
245 proposal.MinerRewardPerThousand = uint64(mrpt)
251 return append(currentBlockProposals, proposal)
254 func (d *Dpos) processEventDeclare(currentBlockDeclares []Declare, txDataInfo []string, tx *bc.Tx, declarer string) []Declare {
256 ProposalHash: bc.Hash{},
261 for i := 0; i < len(txDataInfo[posEventDeclare+1:])/2; i++ {
262 k, v := txDataInfo[posEventDeclare+1+i*2], txDataInfo[posEventDeclare+2+i*2]
265 declare.ProposalHash.UnmarshalText([]byte(v))
268 declare.Decision = true
269 } else if v == "no" {
270 declare.Decision = false
272 return currentBlockDeclares
277 return append(currentBlockDeclares, declare)
280 func (d *Dpos) processEventVote(currentBlockVotes []Vote, stake uint64, voter, to string) []Vote {
281 if stake >= d.config.MinVoterBalance {
282 currentBlockVotes = append(currentBlockVotes, Vote{
288 return currentBlockVotes
291 func (d *Dpos) processEventConfirm(currentBlockConfirmations []Confirmation, c chain.Chain, txDataInfo []string, number uint64, tx *bc.Tx, confirmer string) []Confirmation {
292 if len(txDataInfo) > posEventConfirmNumber {
293 confirmedBlockNumber, err := strconv.Atoi(txDataInfo[posEventConfirmNumber])
294 if err != nil || number-uint64(confirmedBlockNumber) > d.config.MaxSignerCount || number-uint64(confirmedBlockNumber) < 0 {
295 return currentBlockConfirmations
297 confirmedHeader, err := c.GetBlockByHeight(uint64(confirmedBlockNumber))
298 if confirmedHeader == nil {
299 log.Info("Fail to get confirmedHeader")
300 return currentBlockConfirmations
302 confirmedHeaderExtra := HeaderExtra{}
303 if extraVanity+extraSeal > len(confirmedHeader.Extra) {
304 return currentBlockConfirmations
306 if err := json.Unmarshal(confirmedHeader.Extra[extraVanity:len(confirmedHeader.Extra)-extraSeal], &confirmedHeaderExtra); err != nil {
307 log.Info("Fail to decode parent header", "err", err)
308 return currentBlockConfirmations
310 for _, s := range confirmedHeaderExtra.SignerQueue {
312 currentBlockConfirmations = append(currentBlockConfirmations, Confirmation{
314 BlockNumber: uint64(confirmedBlockNumber),
321 return currentBlockConfirmations