OSDN Git Service

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