OSDN Git Service

Support chain tx (#1365)
[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 var (
19         //chainTxUtxoNum maximum utxo quantity in a tx
20         chainTxUtxoNum = 10
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                 })
150
151                 tpls = append(tpls, tpl)
152                 buildAmount = 0
153                 builder = &txbuilder.TemplateBuilder{}
154                 if index == len(utxos)-2 {
155                         break
156                 }
157         }
158         return tpls, utxos[len(utxos)-1], nil
159 }
160
161 // SpendAccountChain build the spend action with auto merge utxo function
162 func SpendAccountChain(ctx context.Context, builder *txbuilder.TemplateBuilder, action txbuilder.Action) ([]*txbuilder.Template, error) {
163         act, ok := action.(*spendAction)
164         if !ok {
165                 return nil, errors.New("fail to convert the spend action")
166         }
167         if *act.AssetId != *consensus.BTMAssetID {
168                 return nil, errors.New("spend chain action only support BTM")
169         }
170
171         utxos, err := act.accounts.reserveBtmUtxoChain(builder, act.AccountID, act.Amount, act.UseUnconfirmed)
172         if err != nil {
173                 return nil, err
174         }
175
176         acct, err := act.accounts.FindByID(act.AccountID)
177         if err != nil {
178                 return nil, err
179         }
180
181         tpls, utxo, err := act.accounts.buildBtmTxChain(utxos, acct.Signer)
182         if err != nil {
183                 return nil, err
184         }
185
186         input, sigInst, err := UtxoToInputs(acct.Signer, utxo)
187         if err != nil {
188                 return nil, err
189         }
190
191         if err := builder.AddInput(input, sigInst); err != nil {
192                 return nil, err
193         }
194
195         if utxo.Amount > act.Amount {
196                 if err = builder.AddOutput(types.NewTxOutput(*consensus.BTMAssetID, utxo.Amount-act.Amount, utxo.ControlProgram)); err != nil {
197                         return nil, errors.Wrap(err, "adding change output")
198                 }
199         }
200         return tpls, nil
201 }
202
203 func (a *spendAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error {
204         var missing []string
205         if a.AccountID == "" {
206                 missing = append(missing, "account_id")
207         }
208         if a.AssetId.IsZero() {
209                 missing = append(missing, "asset_id")
210         }
211         if len(missing) > 0 {
212                 return txbuilder.MissingFieldsError(missing...)
213         }
214
215         acct, err := a.accounts.FindByID(a.AccountID)
216         if err != nil {
217                 return errors.Wrap(err, "get account info")
218         }
219
220         res, err := a.accounts.utxoKeeper.Reserve(a.AccountID, a.AssetId, a.Amount, a.UseUnconfirmed, b.MaxTime())
221         if err != nil {
222                 return errors.Wrap(err, "reserving utxos")
223         }
224
225         // Cancel the reservation if the build gets rolled back.
226         b.OnRollback(func() { a.accounts.utxoKeeper.Cancel(res.id) })
227         for _, r := range res.utxos {
228                 txInput, sigInst, err := UtxoToInputs(acct.Signer, r)
229                 if err != nil {
230                         return errors.Wrap(err, "creating inputs")
231                 }
232
233                 if err = b.AddInput(txInput, sigInst); err != nil {
234                         return errors.Wrap(err, "adding inputs")
235                 }
236         }
237
238         if res.change > 0 {
239                 acp, err := a.accounts.CreateAddress(a.AccountID, true)
240                 if err != nil {
241                         return errors.Wrap(err, "creating control program")
242                 }
243
244                 // Don't insert the control program until callbacks are executed.
245                 a.accounts.insertControlProgramDelayed(b, acp)
246                 if err = b.AddOutput(types.NewTxOutput(*a.AssetId, res.change, acp.ControlProgram)); err != nil {
247                         return errors.Wrap(err, "adding change output")
248                 }
249         }
250         return nil
251 }
252
253 //DecodeSpendUTXOAction unmarshal JSON-encoded data of spend utxo action
254 func (m *Manager) DecodeSpendUTXOAction(data []byte) (txbuilder.Action, error) {
255         a := &spendUTXOAction{accounts: m}
256         return a, json.Unmarshal(data, a)
257 }
258
259 type spendUTXOAction struct {
260         accounts       *Manager
261         OutputID       *bc.Hash                     `json:"output_id"`
262         UseUnconfirmed bool                         `json:"use_unconfirmed"`
263         Arguments      []txbuilder.ContractArgument `json:"arguments"`
264 }
265
266 func (a *spendUTXOAction) ActionType() string {
267         return "spend_account_unspent_output"
268 }
269
270 func (a *spendUTXOAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) error {
271         if a.OutputID == nil {
272                 return txbuilder.MissingFieldsError("output_id")
273         }
274
275         res, err := a.accounts.utxoKeeper.ReserveParticular(*a.OutputID, a.UseUnconfirmed, b.MaxTime())
276         if err != nil {
277                 return err
278         }
279
280         b.OnRollback(func() { a.accounts.utxoKeeper.Cancel(res.id) })
281         var accountSigner *signers.Signer
282         if len(res.utxos[0].AccountID) != 0 {
283                 account, err := a.accounts.FindByID(res.utxos[0].AccountID)
284                 if err != nil {
285                         return err
286                 }
287
288                 accountSigner = account.Signer
289         }
290
291         txInput, sigInst, err := UtxoToInputs(accountSigner, res.utxos[0])
292         if err != nil {
293                 return err
294         }
295
296         if a.Arguments == nil {
297                 return b.AddInput(txInput, sigInst)
298         }
299
300         sigInst = &txbuilder.SigningInstruction{}
301         if err := txbuilder.AddContractArgs(sigInst, a.Arguments); err != nil {
302                 return err
303         }
304
305         return b.AddInput(txInput, sigInst)
306 }
307
308 // UtxoToInputs convert an utxo to the txinput
309 func UtxoToInputs(signer *signers.Signer, u *UTXO) (*types.TxInput, *txbuilder.SigningInstruction, error) {
310         txInput := types.NewSpendInput(nil, u.SourceID, u.AssetID, u.Amount, u.SourcePos, u.ControlProgram)
311         sigInst := &txbuilder.SigningInstruction{}
312         if signer == nil {
313                 return txInput, sigInst, nil
314         }
315
316         path := signers.Path(signer, signers.AccountKeySpace, u.ControlProgramIndex)
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         switch address.(type) {
328         case *common.AddressWitnessPubKeyHash:
329                 sigInst.AddRawWitnessKeys(signer.XPubs, path, signer.Quorum)
330                 derivedXPubs := chainkd.DeriveXPubs(signer.XPubs, path)
331                 derivedPK := derivedXPubs[0].PublicKey()
332                 sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness([]byte(derivedPK)))
333
334         case *common.AddressWitnessScriptHash:
335                 sigInst.AddRawWitnessKeys(signer.XPubs, path, signer.Quorum)
336                 path := signers.Path(signer, signers.AccountKeySpace, u.ControlProgramIndex)
337                 derivedXPubs := chainkd.DeriveXPubs(signer.XPubs, path)
338                 derivedPKs := chainkd.XPubKeys(derivedXPubs)
339                 script, err := vmutil.P2SPMultiSigProgram(derivedPKs, signer.Quorum)
340                 if err != nil {
341                         return nil, nil, err
342                 }
343                 sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness(script))
344
345         default:
346                 return nil, nil, errors.New("unsupport address type")
347         }
348
349         return txInput, sigInst, nil
350 }
351
352 // insertControlProgramDelayed takes a template builder and an account
353 // control program that hasn't been inserted to the database yet. It
354 // registers callbacks on the TemplateBuilder so that all of the template's
355 // account control programs are batch inserted if building the rest of
356 // the template is successful.
357 func (m *Manager) insertControlProgramDelayed(b *txbuilder.TemplateBuilder, acp *CtrlProgram) {
358         m.delayedACPsMu.Lock()
359         m.delayedACPs[b] = append(m.delayedACPs[b], acp)
360         m.delayedACPsMu.Unlock()
361
362         b.OnRollback(func() {
363                 m.delayedACPsMu.Lock()
364                 delete(m.delayedACPs, b)
365                 m.delayedACPsMu.Unlock()
366         })
367         b.OnBuild(func() error {
368                 m.delayedACPsMu.Lock()
369                 acps := m.delayedACPs[b]
370                 delete(m.delayedACPs, b)
371                 m.delayedACPsMu.Unlock()
372
373                 // Insert all of the account control programs at once.
374                 if len(acps) == 0 {
375                         return nil
376                 }
377                 return m.insertControlPrograms(acps...)
378         })
379 }