OSDN Git Service

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