OSDN Git Service

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