OSDN Git Service

add dpos consensus
[bytom/vapor.git] / consensus / consensus / dpos / custom_tx.go
1 package dpos
2
3 import (
4         "encoding/json"
5         "strconv"
6         "strings"
7
8         log "github.com/sirupsen/logrus"
9         "github.com/vapor/chain"
10         "github.com/vapor/protocol/bc"
11         "github.com/vapor/protocol/bc/types"
12 )
13
14 const (
15         /*
16          *  vapor:version:category:action/data
17          */
18         vaporPrefix        = "vapor"
19         vaporVersion       = "1"
20         vaporCategoryEvent = "event"
21         vaporCategoryLog   = "oplog"
22         vaporCategorySC    = "sc"
23         vaporEventVote     = "vote"
24         vaporEventConfirm  = "confirm"
25         vaporEventPorposal = "proposal"
26         vaporEventDeclare  = "declare"
27
28         vaporMinSplitLen      = 3
29         posPrefix             = 0
30         posVersion            = 1
31         posCategory           = 2
32         posEventVote          = 3
33         posEventConfirm       = 3
34         posEventProposal      = 3
35         posEventDeclare       = 3
36         posEventConfirmNumber = 4
37
38         /*
39          *  proposal type
40          */
41         proposalTypeCandidateAdd                  = 1
42         proposalTypeCandidateRemove               = 2
43         proposalTypeMinerRewardDistributionModify = 3 // count in one thousand
44
45         /*
46          * proposal related
47          */
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
51 )
52
53 // Vote :
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
57 type Vote struct {
58         Voter     string `json:"Voter"`
59         Candidate string `json:"Candidate"`
60         Stake     uint64 `json:"Stake"`
61 }
62
63 // Confirmation :
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"`
70 }
71
72 // Proposal :
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
86 }
87
88 func (p *Proposal) copy() *Proposal {
89         cpy := &Proposal{
90                 Hash:                   p.Hash,
91                 ValidationLoopCnt:      p.ValidationLoopCnt,
92                 ImplementNumber:        p.ImplementNumber,
93                 ProposalType:           p.ProposalType,
94                 Proposer:               p.Proposer,
95                 Candidate:              p.Candidate,
96                 MinerRewardPerThousand: p.MinerRewardPerThousand,
97                 Declares:               make([]*Declare, len(p.Declares)),
98                 ReceivedNumber:         p.ReceivedNumber,
99         }
100
101         copy(cpy.Declares, p.Declares)
102         return cpy
103 }
104
105 // Declare :
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"`
113 }
114
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"`
126 }
127
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) {
130
131         var (
132                 snap   *Snapshot
133                 err    error
134                 height uint64
135         )
136         height = header.Height
137         if height > 1 {
138                 snap, err = d.snapshot(c, height-1, header.PreviousBlockHash, nil, nil, defaultLoopCntRecalculateSigners)
139                 if err != nil {
140                         return headerExtra, err
141                 }
142         }
143
144         for _, tx := range txs {
145                 var (
146                         from string
147                         to   string
148                 )
149                 dpos := new(bc.Dpos)
150                 stake := uint64(0)
151                 for _, value := range tx.Entries {
152                         switch d := value.(type) {
153                         case *bc.Dpos:
154                                 from = d.From
155                                 to = d.To
156                                 dpos = d
157                                 stake = d.Stake
158                         default:
159                                 continue
160                         }
161
162                         if len(dpos.Data) >= len(vaporPrefix) {
163                                 txData := dpos.Data
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)
177
178                                                         }
179                                                 } else {
180                                                         // todo : something wrong, leave this transaction to process as normal transaction
181                                                 }
182
183                                         case vaporCategoryLog:
184                                                 // todo :
185                                         case vaporCategorySC:
186                                                 // todo :
187                                         }
188                                 }
189                         }
190                         /*
191                                 if height > 1 {
192                                         headerExtra.ModifyPredecessorVotes = d.processPredecessorVoter(headerExtra.ModifyPredecessorVotes, stake, from, to, snap)
193                                 }
194                         */
195                 }
196         }
197
198         return headerExtra, nil
199 }
200
201 func (d *Dpos) processEventProposal(currentBlockProposals []Proposal, txDataInfo []string, tx *bc.Tx, proposer string) []Proposal {
202         proposal := Proposal{
203                 Hash:                   tx.ID,
204                 ValidationLoopCnt:      defaultValidationLoopCnt,
205                 ImplementNumber:        uint64(1),
206                 ProposalType:           proposalTypeCandidateAdd,
207                 Proposer:               proposer,
208                 MinerRewardPerThousand: minerRewardPerThousand,
209                 Declares:               []*Declare{},
210                 ReceivedNumber:         uint64(0),
211         }
212
213         for i := 0; i < len(txDataInfo[posEventProposal+1:])/2; i++ {
214                 k, v := txDataInfo[posEventProposal+1+i*2], txDataInfo[posEventProposal+2+i*2]
215                 switch k {
216                 case "vlcnt":
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
220                         } else {
221                                 proposal.ValidationLoopCnt = uint64(validationLoopCnt)
222                         }
223                 case "implement_number":
224                         if implementNumber, err := strconv.Atoi(v); err != nil || implementNumber <= 0 {
225                                 return currentBlockProposals
226                         } else {
227                                 proposal.ImplementNumber = uint64(implementNumber)
228                         }
229                 case "proposal_type":
230                         if proposalType, err := strconv.Atoi(v); err != nil || (proposalType != proposalTypeCandidateAdd && proposalType != proposalTypeCandidateRemove && proposalType != proposalTypeMinerRewardDistributionModify) {
231                                 return currentBlockProposals
232                         } else {
233                                 proposal.ProposalType = uint64(proposalType)
234                         }
235                 case "candidate":
236                         // not check here
237                         //proposal.Candidate.UnmarshalText([]byte(v))
238                         /*
239                                 address, err := common.DecodeAddress(v, &consensus.ActiveNetParams)
240                                 if err != nil {
241                                         return currentBlockProposals
242                                 }
243                         */
244                         proposal.Candidate = v
245                 case "mrpt":
246                         // miner reward per thousand
247                         if mrpt, err := strconv.Atoi(v); err != nil || mrpt < 0 || mrpt > 1000 {
248                                 return currentBlockProposals
249                         } else {
250                                 proposal.MinerRewardPerThousand = uint64(mrpt)
251                         }
252
253                 }
254         }
255
256         return append(currentBlockProposals, proposal)
257 }
258
259 func (d *Dpos) processEventDeclare(currentBlockDeclares []Declare, txDataInfo []string, tx *bc.Tx, declarer string) []Declare {
260         declare := Declare{
261                 ProposalHash: bc.Hash{},
262                 Declarer:     declarer,
263                 Decision:     true,
264         }
265
266         for i := 0; i < len(txDataInfo[posEventDeclare+1:])/2; i++ {
267                 k, v := txDataInfo[posEventDeclare+1+i*2], txDataInfo[posEventDeclare+2+i*2]
268                 switch k {
269                 case "hash":
270                         declare.ProposalHash.UnmarshalText([]byte(v))
271                 case "decision":
272                         if v == "yes" {
273                                 declare.Decision = true
274                         } else if v == "no" {
275                                 declare.Decision = false
276                         } else {
277                                 return currentBlockDeclares
278                         }
279                 }
280         }
281
282         return append(currentBlockDeclares, declare)
283 }
284
285 func (d *Dpos) processEventVote(currentBlockVotes []Vote, stake uint64, voter, to string) []Vote {
286
287         //if new(big.Int).SetUint64(stake).Cmp(d.config.MinVoterBalance) > 0 {
288         currentBlockVotes = append(currentBlockVotes, Vote{
289                 Voter:     voter,
290                 Candidate: to,
291                 Stake:     stake,
292         })
293         //}
294         return currentBlockVotes
295 }
296
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
302                 }
303                 confirmedHeader, err := c.GetBlockByHeight(uint64(confirmedBlockNumber))
304                 if confirmedHeader == nil {
305                         log.Info("Fail to get confirmedHeader")
306                         return currentBlockConfirmations
307                 }
308                 confirmedHeaderExtra := HeaderExtra{}
309                 if extraVanity+extraSeal > len(confirmedHeader.Extra) {
310                         return currentBlockConfirmations
311                 }
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
316                 }
317                 for _, s := range confirmedHeaderExtra.SignerQueue {
318                         if s == confirmer {
319                                 currentBlockConfirmations = append(currentBlockConfirmations, Confirmation{
320                                         Signer:      confirmer,
321                                         BlockNumber: uint64(confirmedBlockNumber),
322                                 })
323                                 break
324                         }
325                 }
326         }
327
328         return currentBlockConfirmations
329 }
330
331 func (d *Dpos) processPredecessorVoter(modifyPredecessorVotes []Vote, stake uint64, voter, to string, snap *Snapshot) []Vote {
332         if stake > 0 {
333                 if snap.isVoter(voter) {
334                         modifyPredecessorVotes = append(modifyPredecessorVotes, Vote{
335                                 Voter:     voter,
336                                 Candidate: "",
337                                 Stake:     stake,
338                         })
339                 }
340                 if snap.isVoter(to) {
341                         modifyPredecessorVotes = append(modifyPredecessorVotes, Vote{
342                                 Voter:     to,
343                                 Candidate: "",
344                                 Stake:     stake,
345                         })
346                 }
347
348         }
349         return modifyPredecessorVotes
350 }