OSDN Git Service

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