OSDN Git Service

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