OSDN Git Service

Add the implementation for dppos
[bytom/vapor.git] / account / dpos_builder.go
1 package account
2
3 import (
4         "context"
5         "encoding/json"
6         "fmt"
7
8         "github.com/vapor/config"
9
10         "github.com/vapor/blockchain/txbuilder"
11         "github.com/vapor/common"
12         "github.com/vapor/consensus"
13         dpos "github.com/vapor/consensus/consensus/dpos"
14         "github.com/vapor/crypto/ed25519/chainkd"
15         "github.com/vapor/errors"
16         "github.com/vapor/protocol/bc"
17         "github.com/vapor/protocol/bc/types"
18         "github.com/vapor/protocol/vm"
19         "github.com/vapor/protocol/vm/vmutil"
20 )
21
22 func (m *Manager) DecodeDposAction(data []byte) (txbuilder.Action, error) {
23         a := &DopsAction{Accounts: m}
24         err := json.Unmarshal(data, a)
25         return a, err
26 }
27
28 type DopsAction struct {
29         Accounts *Manager
30         bc.AssetAmount
31         DposType       uint32   `json:"dpos_type"`
32         Address        string   `json:"address"`
33         Name           string   `json:"name"`
34         Forgers        []string `json:"forgers"`
35         UseUnconfirmed bool     `json:"use_unconfirmed"`
36 }
37
38 func (a *DopsAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error {
39         var missing []string
40
41         if a.AssetId.IsZero() {
42                 missing = append(missing, "asset_id")
43         }
44         if a.Address == "" {
45                 missing = append(missing, "address")
46         }
47         if len(missing) > 0 {
48                 return txbuilder.MissingFieldsError(missing...)
49         }
50         if types.TxType(a.DposType) < types.Binary || types.TxType(a.DposType) > types.CancelVote {
51                 return errors.New("tx type  of dpos is error")
52         }
53         var (
54                 referenceData []byte
55                 data          []byte
56                 op            vm.Op
57                 err           error
58         )
59
60         switch types.TxType(a.DposType) {
61         case types.Binary:
62         case types.Registe:
63                 if a.Name == "" {
64                         return errors.New("name is null for dpos Registe")
65                 }
66                 if a.Amount < consensus.RegisrerForgerFee {
67                         return errors.New("The transaction fee is 100000000 for dpos Registe")
68                 }
69
70                 if dpos.GDpos.HaveDelegate(a.Name, a.Address) {
71                         return errors.New("Forger name has registe")
72                 }
73
74                 data, err = json.Marshal(&dpos.RegisterForgerData{Name: a.Name})
75                 if err != nil {
76                         return err
77                 }
78                 op = vm.OP_REGISTE
79         case types.Vote:
80                 if len(a.Forgers) == 0 {
81                         return errors.New("Forgers is null for dpos Vote")
82                 }
83
84                 if a.Amount < consensus.VoteForgerFee {
85                         return errors.New("The transaction fee is 10000000 for dpos Registe")
86                 }
87
88                 for _, forger := range a.Forgers {
89                         if dpos.GDpos.HaveVote(a.Address, forger) {
90                                 return fmt.Errorf("delegate name: %s is voted", forger)
91                         }
92                 }
93
94                 data, err = json.Marshal(&dpos.VoteForgerData{Forgers: a.Forgers})
95                 if err != nil {
96                         return err
97                 }
98                 op = vm.OP_VOTE
99         case types.CancelVote:
100                 if len(a.Forgers) == 0 {
101                         return errors.New("Forgers is null for dpos CancelVote")
102                 }
103                 if a.Amount < consensus.CancelVoteForgerFee {
104                         return errors.New("The transaction fee is 10000000 for dpos Registe")
105                 }
106
107                 for _, forger := range a.Forgers {
108                         if !dpos.GDpos.HaveVote(a.Address, forger) {
109                                 return fmt.Errorf("delegate name: %s is not voted", forger)
110                         }
111                 }
112
113                 data, err = json.Marshal(&dpos.CancelVoteForgerData{Forgers: a.Forgers})
114                 if err != nil {
115                         return err
116                 }
117                 op = vm.OP_REVOKE
118         }
119
120         msg := dpos.DposMsg{
121                 Type: op,
122                 Data: data,
123         }
124
125         referenceData, err = json.Marshal(&msg)
126         if err != nil {
127                 return err
128         }
129         b.SetReferenceData(referenceData)
130
131         res, err := a.Accounts.utxoKeeper.ReserveByAddress(a.Address, a.AssetId, a.Amount, a.UseUnconfirmed, false)
132         if err != nil {
133                 return errors.Wrap(err, "reserving utxos")
134         }
135
136         // Cancel the reservation if the build gets rolled back.
137         b.OnRollback(func() { a.Accounts.utxoKeeper.Cancel(res.id) })
138         for _, r := range res.utxos {
139                 txSpendInput, sigInst, err := spendInput(r)
140                 if err != nil {
141                         return errors.Wrap(err, "creating inputs")
142                 }
143
144                 if err = b.AddInput(txSpendInput, sigInst); err != nil {
145                         return errors.Wrap(err, "adding inputs")
146                 }
147         }
148         if res.change >= 0 {
149                 address, err := common.DecodeAddress(a.Address, &consensus.ActiveNetParams)
150                 if err != nil {
151                         return err
152                 }
153                 redeemContract := address.ScriptAddress()
154                 program, err := vmutil.P2WPKHProgram(redeemContract)
155                 if err != nil {
156                         return err
157                 }
158                 if err = b.AddOutput(types.NewTxOutput(*consensus.BTMAssetID, res.change, program)); err != nil {
159                         return errors.Wrap(err, "adding change output")
160                 }
161         }
162
163         return nil
164 }
165
166 func (a *DopsAction) ActionType() string {
167         return "dpos"
168 }
169
170 // DposInputs convert an utxo to the txinput
171 func DposTx(from, to string, stake uint64, u *UTXO, txType types.TxType, h uint64) (*types.TxInput, *txbuilder.SigningInstruction, error) {
172         txInput := types.NewDpos(nil, from, to, u.SourceID, u.AssetID, stake, u.Amount, u.SourcePos, u.ControlProgram, txType, h)
173         sigInst := &txbuilder.SigningInstruction{}
174         var xpubs []chainkd.XPub
175         var xprv chainkd.XPrv
176         xprv.UnmarshalText([]byte(config.CommonConfig.Consensus.XPrv))
177         xpubs = append(xpubs, xprv.XPub())
178         quorum := len(xpubs)
179         if u.Address == "" {
180                 sigInst.AddWitnessKeysWithOutPath(xpubs, quorum)
181                 return txInput, sigInst, nil
182         }
183
184         address, err := common.DecodeAddress(u.Address, &consensus.ActiveNetParams)
185         if err != nil {
186                 return nil, nil, err
187         }
188         sigInst.AddRawWitnessKeysWithoutPath(xpubs, quorum)
189         switch address.(type) {
190         case *common.AddressWitnessPubKeyHash:
191                 derivedPK := xpubs[0].PublicKey()
192                 sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness([]byte(derivedPK)))
193
194         case *common.AddressWitnessScriptHash:
195                 derivedXPubs := xpubs
196                 derivedPKs := chainkd.XPubKeys(derivedXPubs)
197                 script, err := vmutil.P2SPMultiSigProgram(derivedPKs, quorum)
198                 if err != nil {
199                         return nil, nil, err
200                 }
201                 sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness(script))
202
203         default:
204                 return nil, nil, errors.New("unsupport address type")
205         }
206
207         return txInput, sigInst, nil
208 }
209
210 // spendInput convert an utxo to the txinput
211 func spendInput(u *UTXO) (*types.TxInput, *txbuilder.SigningInstruction, error) {
212         txSpendInput := types.NewSpendInput(nil, u.SourceID, u.AssetID, u.Amount, u.SourcePos, u.ControlProgram)
213         sigInst := &txbuilder.SigningInstruction{}
214         var xpubs []chainkd.XPub
215         var xprv chainkd.XPrv
216         xprv.UnmarshalText([]byte(config.CommonConfig.Consensus.XPrv))
217         xpubs = append(xpubs, xprv.XPub())
218         quorum := len(xpubs)
219         if u.Address == "" {
220                 sigInst.AddWitnessKeysWithOutPath(xpubs, quorum)
221                 return txSpendInput, sigInst, nil
222         }
223
224         address, err := common.DecodeAddress(u.Address, &consensus.ActiveNetParams)
225         if err != nil {
226                 return nil, nil, err
227         }
228         sigInst.AddRawWitnessKeysWithoutPath(xpubs, quorum)
229         switch address.(type) {
230         case *common.AddressWitnessPubKeyHash:
231                 derivedPK := xpubs[0].PublicKey()
232                 sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness([]byte(derivedPK)))
233
234         case *common.AddressWitnessScriptHash:
235                 derivedXPubs := xpubs
236                 derivedPKs := chainkd.XPubKeys(derivedXPubs)
237                 script, err := vmutil.P2SPMultiSigProgram(derivedPKs, quorum)
238                 if err != nil {
239                         return nil, nil, err
240                 }
241                 sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness(script))
242
243         default:
244                 return nil, nil, errors.New("unsupport address type")
245         }
246
247         return txSpendInput, sigInst, nil
248 }