OSDN Git Service

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