OSDN Git Service

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