OSDN Git Service

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