OSDN Git Service

Small edit (#241)
[bytom/vapor.git] / account / builder.go
1 package account
2
3 import (
4         "context"
5         stdjson "encoding/json"
6
7         "github.com/vapor/blockchain/signers"
8         "github.com/vapor/blockchain/txbuilder"
9         "github.com/vapor/common"
10         "github.com/vapor/consensus"
11         "github.com/vapor/crypto/ed25519/chainkd"
12         "github.com/vapor/encoding/json"
13         "github.com/vapor/errors"
14         "github.com/vapor/protocol/bc"
15         "github.com/vapor/protocol/bc/types"
16         "github.com/vapor/protocol/vm/vmutil"
17 )
18
19 var (
20         //chainTxUtxoNum maximum utxo quantity in a tx
21         chainTxUtxoNum = 5
22         //chainTxMergeGas chain tx gas
23         chainTxMergeGas = uint64(10000000)
24 )
25
26 //DecodeSpendAction unmarshal JSON-encoded data of spend action
27 func (m *Manager) DecodeSpendAction(data []byte) (txbuilder.Action, error) {
28         a := &spendAction{accounts: m}
29         return a, stdjson.Unmarshal(data, a)
30 }
31
32 type spendAction struct {
33         accounts *Manager
34         bc.AssetAmount
35         AccountID      string `json:"account_id"`
36         UseUnconfirmed bool   `json:"use_unconfirmed"`
37 }
38
39 func (a *spendAction) ActionType() string {
40         return "spend_account"
41 }
42
43 // MergeSpendAction merge common assetID and accountID spend action
44 func MergeSpendAction(actions []txbuilder.Action) []txbuilder.Action {
45         resultActions := []txbuilder.Action{}
46         spendActionMap := make(map[string]*spendAction)
47
48         for _, act := range actions {
49                 switch act := act.(type) {
50                 case *spendAction:
51                         actionKey := act.AssetId.String() + act.AccountID
52                         if tmpAct, ok := spendActionMap[actionKey]; ok {
53                                 tmpAct.Amount += act.Amount
54                                 tmpAct.UseUnconfirmed = tmpAct.UseUnconfirmed || act.UseUnconfirmed
55                         } else {
56                                 spendActionMap[actionKey] = act
57                                 resultActions = append(resultActions, act)
58                         }
59                 default:
60                         resultActions = append(resultActions, act)
61                 }
62         }
63         return resultActions
64 }
65
66 //calcMergeGas calculate the gas required that n utxos are merged into one
67 func calcMergeGas(num int) uint64 {
68         gas := uint64(0)
69         for num > 1 {
70                 gas += chainTxMergeGas
71                 num -= chainTxUtxoNum - 1
72         }
73         return gas
74 }
75
76 func (m *Manager) reserveBtmUtxoChain(builder *txbuilder.TemplateBuilder, accountID string, amount uint64, useUnconfirmed bool) ([]*UTXO, error) {
77         reservedAmount := uint64(0)
78         utxos := []*UTXO{}
79         for gasAmount := uint64(0); reservedAmount < gasAmount+amount; gasAmount = calcMergeGas(len(utxos)) {
80                 reserveAmount := amount + gasAmount - reservedAmount
81                 res, err := m.utxoKeeper.Reserve(accountID, consensus.BTMAssetID, reserveAmount, useUnconfirmed, nil, builder.MaxTime())
82                 if err != nil {
83                         return nil, err
84                 }
85
86                 builder.OnRollback(func() { m.utxoKeeper.Cancel(res.id) })
87                 reservedAmount += reserveAmount + res.change
88                 utxos = append(utxos, res.utxos[:]...)
89         }
90         return utxos, nil
91 }
92
93 func (m *Manager) buildBtmTxChain(utxos []*UTXO, signer *signers.Signer) ([]*txbuilder.Template, *UTXO, error) {
94         if len(utxos) == 0 {
95                 return nil, nil, errors.New("mergeSpendActionUTXO utxos num 0")
96         }
97
98         tpls := []*txbuilder.Template{}
99         if len(utxos) == 1 {
100                 return tpls, utxos[len(utxos)-1], nil
101         }
102
103         acp, err := m.GetLocalCtrlProgramByAddress(utxos[0].Address)
104         if err != nil {
105                 return nil, nil, err
106         }
107
108         buildAmount := uint64(0)
109         builder := &txbuilder.TemplateBuilder{}
110         for index := 0; index < len(utxos); index++ {
111                 input, sigInst, err := UtxoToInputs(signer, utxos[index])
112                 if err != nil {
113                         return nil, nil, err
114                 }
115
116                 if err = builder.AddInput(input, sigInst); err != nil {
117                         return nil, nil, err
118                 }
119
120                 buildAmount += input.Amount()
121                 if builder.InputCount() != chainTxUtxoNum && index != len(utxos)-1 {
122                         continue
123                 }
124
125                 outAmount := buildAmount - chainTxMergeGas
126                 output := types.NewIntraChainOutput(*consensus.BTMAssetID, outAmount, acp.ControlProgram)
127                 if err := builder.AddOutput(output); err != nil {
128                         return nil, nil, err
129                 }
130
131                 tpl, _, err := builder.Build()
132                 if err != nil {
133                         return nil, nil, err
134                 }
135
136                 bcOut, err := tpl.Transaction.IntraChainOutput(*tpl.Transaction.ResultIds[0])
137                 if err != nil {
138                         return nil, nil, err
139                 }
140
141                 utxos = append(utxos, &UTXO{
142                         OutputID:            *tpl.Transaction.ResultIds[0],
143                         AssetID:             *consensus.BTMAssetID,
144                         Amount:              outAmount,
145                         ControlProgram:      acp.ControlProgram,
146                         SourceID:            *bcOut.Source.Ref,
147                         SourcePos:           bcOut.Source.Position,
148                         ControlProgramIndex: acp.KeyIndex,
149                         Address:             acp.Address,
150                         Change:              acp.Change,
151                 })
152
153                 tpls = append(tpls, tpl)
154                 buildAmount = 0
155                 builder = &txbuilder.TemplateBuilder{}
156                 if index == len(utxos)-2 {
157                         break
158                 }
159         }
160         return tpls, utxos[len(utxos)-1], nil
161 }
162
163 // SpendAccountChain build the spend action with auto merge utxo function
164 func SpendAccountChain(ctx context.Context, builder *txbuilder.TemplateBuilder, action txbuilder.Action) ([]*txbuilder.Template, error) {
165         act, ok := action.(*spendAction)
166         if !ok {
167                 return nil, errors.New("fail to convert the spend action")
168         }
169         if *act.AssetId != *consensus.BTMAssetID {
170                 return nil, errors.New("spend chain action only support BTM")
171         }
172
173         utxos, err := act.accounts.reserveBtmUtxoChain(builder, act.AccountID, act.Amount, act.UseUnconfirmed)
174         if err != nil {
175                 return nil, err
176         }
177
178         acct, err := act.accounts.FindByID(act.AccountID)
179         if err != nil {
180                 return nil, err
181         }
182
183         tpls, utxo, err := act.accounts.buildBtmTxChain(utxos, acct.Signer)
184         if err != nil {
185                 return nil, err
186         }
187
188         input, sigInst, err := UtxoToInputs(acct.Signer, utxo)
189         if err != nil {
190                 return nil, err
191         }
192
193         if err := builder.AddInput(input, sigInst); err != nil {
194                 return nil, err
195         }
196
197         if utxo.Amount > act.Amount {
198                 if err = builder.AddOutput(types.NewIntraChainOutput(*consensus.BTMAssetID, utxo.Amount-act.Amount, utxo.ControlProgram)); err != nil {
199                         return nil, errors.Wrap(err, "adding change output")
200                 }
201         }
202         return tpls, nil
203 }
204
205 func (a *spendAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error {
206         var missing []string
207         if a.AccountID == "" {
208                 missing = append(missing, "account_id")
209         }
210         if a.AssetId.IsZero() {
211                 missing = append(missing, "asset_id")
212         }
213         if len(missing) > 0 {
214                 return txbuilder.MissingFieldsError(missing...)
215         }
216
217         acct, err := a.accounts.FindByID(a.AccountID)
218         if err != nil {
219                 return errors.Wrap(err, "get account info")
220         }
221
222         res, err := a.accounts.utxoKeeper.Reserve(a.AccountID, a.AssetId, a.Amount, a.UseUnconfirmed, nil, b.MaxTime())
223         if err != nil {
224                 return errors.Wrap(err, "reserving utxos")
225         }
226
227         // Cancel the reservation if the build gets rolled back.
228         b.OnRollback(func() { a.accounts.utxoKeeper.Cancel(res.id) })
229         for _, r := range res.utxos {
230                 txInput, sigInst, err := UtxoToInputs(acct.Signer, r)
231                 if err != nil {
232                         return errors.Wrap(err, "creating inputs")
233                 }
234
235                 if err = b.AddInput(txInput, sigInst); err != nil {
236                         return errors.Wrap(err, "adding inputs")
237                 }
238         }
239
240         if res.change > 0 {
241                 acp, err := a.accounts.CreateAddress(a.AccountID, true)
242                 if err != nil {
243                         return errors.Wrap(err, "creating control program")
244                 }
245
246                 // Don't insert the control program until callbacks are executed.
247                 a.accounts.insertControlProgramDelayed(b, acp)
248                 if err = b.AddOutput(types.NewIntraChainOutput(*a.AssetId, res.change, acp.ControlProgram)); err != nil {
249                         return errors.Wrap(err, "adding change output")
250                 }
251         }
252         return nil
253 }
254
255 //DecodeSpendUTXOAction unmarshal JSON-encoded data of spend utxo action
256 func (m *Manager) DecodeSpendUTXOAction(data []byte) (txbuilder.Action, error) {
257         a := &spendUTXOAction{accounts: m}
258         return a, stdjson.Unmarshal(data, a)
259 }
260
261 type spendUTXOAction struct {
262         accounts       *Manager
263         OutputID       *bc.Hash                     `json:"output_id"`
264         UseUnconfirmed bool                         `json:"use_unconfirmed"`
265         Arguments      []txbuilder.ContractArgument `json:"arguments"`
266 }
267
268 func (a *spendUTXOAction) ActionType() string {
269         return "spend_account_unspent_output"
270 }
271
272 func (a *spendUTXOAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error {
273         if a.OutputID == nil {
274                 return txbuilder.MissingFieldsError("output_id")
275         }
276
277         res, err := a.accounts.utxoKeeper.ReserveParticular(*a.OutputID, a.UseUnconfirmed, b.MaxTime())
278         if err != nil {
279                 return err
280         }
281
282         b.OnRollback(func() { a.accounts.utxoKeeper.Cancel(res.id) })
283         var accountSigner *signers.Signer
284         if len(res.utxos[0].AccountID) != 0 {
285                 account, err := a.accounts.FindByID(res.utxos[0].AccountID)
286                 if err != nil {
287                         return err
288                 }
289
290                 accountSigner = account.Signer
291         }
292
293         txInput, sigInst, err := UtxoToInputs(accountSigner, res.utxos[0])
294         if err != nil {
295                 return err
296         }
297
298         if a.Arguments == nil {
299                 return b.AddInput(txInput, sigInst)
300         }
301
302         sigInst = &txbuilder.SigningInstruction{}
303         if err := txbuilder.AddContractArgs(sigInst, a.Arguments); err != nil {
304                 return err
305         }
306
307         return b.AddInput(txInput, sigInst)
308 }
309
310 // UtxoToInputs convert an utxo to the txinput
311 func UtxoToInputs(signer *signers.Signer, u *UTXO) (*types.TxInput, *txbuilder.SigningInstruction, error) {
312         txInput := &types.TxInput{}
313         if u.Vote == nil {
314                 txInput = types.NewSpendInput(nil, u.SourceID, u.AssetID, u.Amount, u.SourcePos, u.ControlProgram)
315         } else {
316                 txInput = types.NewVetoInput(nil, u.SourceID, u.AssetID, u.Amount, u.SourcePos, u.ControlProgram, u.Vote)
317         }
318         sigInst := &txbuilder.SigningInstruction{}
319         if signer == nil {
320                 return txInput, sigInst, nil
321         }
322
323         path, err := signers.Path(signer, signers.AccountKeySpace, u.Change, u.ControlProgramIndex)
324         if err != nil {
325                 return nil, nil, err
326         }
327         if u.Address == "" {
328                 sigInst.AddWitnessKeys(signer.XPubs, path, signer.Quorum)
329                 return txInput, sigInst, nil
330         }
331
332         address, err := common.DecodeAddress(u.Address, &consensus.ActiveNetParams)
333         if err != nil {
334                 return nil, nil, err
335         }
336
337         sigInst.AddRawWitnessKeys(signer.XPubs, path, signer.Quorum)
338         derivedXPubs := chainkd.DeriveXPubs(signer.XPubs, path)
339
340         switch address.(type) {
341         case *common.AddressWitnessPubKeyHash:
342                 derivedPK := derivedXPubs[0].PublicKey()
343                 sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness([]byte(derivedPK)))
344
345         case *common.AddressWitnessScriptHash:
346                 derivedPKs := chainkd.XPubKeys(derivedXPubs)
347                 script, err := vmutil.P2SPMultiSigProgram(derivedPKs, signer.Quorum)
348                 if err != nil {
349                         return nil, nil, err
350                 }
351                 sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness(script))
352
353         default:
354                 return nil, nil, errors.New("unsupport address type")
355         }
356
357         return txInput, sigInst, nil
358 }
359
360 // insertControlProgramDelayed takes a template builder and an account
361 // control program that hasn't been inserted to the database yet. It
362 // registers callbacks on the TemplateBuilder so that all of the template's
363 // account control programs are batch inserted if building the rest of
364 // the template is successful.
365 func (m *Manager) insertControlProgramDelayed(b *txbuilder.TemplateBuilder, acp *CtrlProgram) {
366         m.delayedACPsMu.Lock()
367         m.delayedACPs[b] = append(m.delayedACPs[b], acp)
368         m.delayedACPsMu.Unlock()
369
370         b.OnRollback(func() {
371                 m.delayedACPsMu.Lock()
372                 delete(m.delayedACPs, b)
373                 m.delayedACPsMu.Unlock()
374         })
375         b.OnBuild(func() error {
376                 m.delayedACPsMu.Lock()
377                 acps := m.delayedACPs[b]
378                 delete(m.delayedACPs, b)
379                 m.delayedACPsMu.Unlock()
380
381                 // Insert all of the account control programs at once.
382                 if len(acps) == 0 {
383                         return nil
384                 }
385                 return m.SaveControlPrograms(acps...)
386         })
387 }
388
389 //DecodeVetoAction unmarshal JSON-encoded data of spend action
390 func (m *Manager) DecodeVetoAction(data []byte) (txbuilder.Action, error) {
391         a := &vetoAction{accounts: m}
392         return a, stdjson.Unmarshal(data, a)
393 }
394
395 type vetoAction struct {
396         accounts *Manager
397         bc.AssetAmount
398         AccountID      string        `json:"account_id"`
399         Vote           json.HexBytes `json:"vote"`
400         UseUnconfirmed bool          `json:"use_unconfirmed"`
401 }
402
403 func (a *vetoAction) ActionType() string {
404         return "veto"
405 }
406
407 func (a *vetoAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error {
408         var missing []string
409         if a.AccountID == "" {
410                 missing = append(missing, "account_id")
411         }
412         if a.AssetId.IsZero() {
413                 missing = append(missing, "asset_id")
414         }
415         if len(missing) > 0 {
416                 return txbuilder.MissingFieldsError(missing...)
417         }
418
419         acct, err := a.accounts.FindByID(a.AccountID)
420         if err != nil {
421                 return errors.Wrap(err, "get account info")
422         }
423
424         res, err := a.accounts.utxoKeeper.Reserve(a.AccountID, a.AssetId, a.Amount, a.UseUnconfirmed, a.Vote, b.MaxTime())
425         if err != nil {
426                 return errors.Wrap(err, "reserving utxos")
427         }
428
429         // Cancel the reservation if the build gets rolled back.
430         b.OnRollback(func() { a.accounts.utxoKeeper.Cancel(res.id) })
431         for _, r := range res.utxos {
432                 txInput, sigInst, err := UtxoToInputs(acct.Signer, r)
433                 if err != nil {
434                         return errors.Wrap(err, "creating inputs")
435                 }
436
437                 if err = b.AddInput(txInput, sigInst); err != nil {
438                         return errors.Wrap(err, "adding inputs")
439                 }
440         }
441
442         if res.change > 0 {
443                 acp, err := a.accounts.CreateAddress(a.AccountID, true)
444                 if err != nil {
445                         return errors.Wrap(err, "creating control program")
446                 }
447
448                 // Don't insert the control program until callbacks are executed.
449                 a.accounts.insertControlProgramDelayed(b, acp)
450                 if err = b.AddOutput(types.NewVoteOutput(*a.AssetId, res.change, acp.ControlProgram, a.Vote)); err != nil {
451                         return errors.Wrap(err, "adding change voteOutput")
452                 }
453         }
454         return nil
455 }