OSDN Git Service

Merge pull request #41 from Bytom/dev
[bytom/vapor.git] / api / transact.go
1 package api
2
3 import (
4         "context"
5         "encoding/json"
6         "math"
7         "strings"
8         "time"
9
10         log "github.com/sirupsen/logrus"
11
12         "github.com/vapor/account"
13         "github.com/vapor/blockchain/txbuilder"
14         "github.com/vapor/consensus"
15         "github.com/vapor/consensus/segwit"
16         "github.com/vapor/errors"
17         "github.com/vapor/math/checked"
18         "github.com/vapor/net/http/reqid"
19         "github.com/vapor/protocol/bc"
20         "github.com/vapor/protocol/bc/types"
21 )
22
23 var (
24         defaultTxTTL    = 5 * time.Minute
25         defaultBaseRate = float64(100000)
26         flexibleGas     = int64(1800)
27 )
28
29 func (a *API) actionDecoder(action string) (func([]byte) (txbuilder.Action, error), bool) {
30         decoders := map[string]func([]byte) (txbuilder.Action, error){
31                 "control_address":              txbuilder.DecodeControlAddressAction,
32                 "control_program":              txbuilder.DecodeControlProgramAction,
33                 "issue":                        a.wallet.AssetReg.DecodeIssueAction,
34                 "retire":                       txbuilder.DecodeRetireAction,
35                 "spend_account":                a.wallet.AccountMgr.DecodeSpendAction,
36                 "spend_account_unspent_output": a.wallet.AccountMgr.DecodeSpendUTXOAction,
37                 "dpos":                         a.wallet.AccountMgr.DecodeDposAction,
38                 "ipfs_data":                    txbuilder.DecodeIpfsDataAction,
39         }
40         decoder, ok := decoders[action]
41         return decoder, ok
42 }
43
44 func onlyHaveInputActions(req *BuildRequest) (bool, error) {
45         count := 0
46         dpos := false
47         for i, act := range req.Actions {
48                 actionType, ok := act["type"].(string)
49                 if !ok {
50                         return false, errors.WithDetailf(ErrBadActionType, "no action type provided on action %d", i)
51                 }
52                 if strings.HasPrefix(actionType, "dpos") {
53                         dpos = true
54                 }
55                 if strings.HasPrefix(actionType, "spend") || actionType == "issue" {
56                         count++
57                 }
58         }
59
60         if dpos == true && count == 0 {
61                 return false, nil
62         }
63
64         return count == len(req.Actions), nil
65 }
66
67 func (a *API) buildSingle(ctx context.Context, req *BuildRequest) (*txbuilder.Template, error) {
68         if err := a.checkRequestValidity(ctx, req); err != nil {
69                 return nil, err
70         }
71         actions, err := a.mergeSpendActions(req)
72         if err != nil {
73                 return nil, err
74         }
75
76         maxTime := time.Now().Add(req.TTL.Duration)
77         tpl, err := txbuilder.Build(ctx, req.Tx, actions, maxTime, req.TimeRange)
78         if errors.Root(err) == txbuilder.ErrAction {
79                 // append each of the inner errors contained in the data.
80                 var Errs string
81                 var rootErr error
82                 for i, innerErr := range errors.Data(err)["actions"].([]error) {
83                         if i == 0 {
84                                 rootErr = errors.Root(innerErr)
85                         }
86                         Errs = Errs + innerErr.Error()
87                 }
88                 err = errors.WithDetail(rootErr, Errs)
89         }
90         if err != nil {
91                 return nil, err
92         }
93
94         // ensure null is never returned for signing instructions
95         if tpl.SigningInstructions == nil {
96                 tpl.SigningInstructions = []*txbuilder.SigningInstruction{}
97         }
98         return tpl, nil
99 }
100
101 // POST /build-transaction
102 func (a *API) build(ctx context.Context, buildReqs *BuildRequest) Response {
103         subctx := reqid.NewSubContext(ctx, reqid.New())
104         tmpl, err := a.buildSingle(subctx, buildReqs)
105         if err != nil {
106                 return NewErrorResponse(err)
107         }
108
109         return NewSuccessResponse(tmpl)
110 }
111 func (a *API) checkRequestValidity(ctx context.Context, req *BuildRequest) error {
112         if err := a.completeMissingIDs(ctx, req); err != nil {
113                 return err
114         }
115
116         if req.TTL.Duration == 0 {
117                 req.TTL.Duration = defaultTxTTL
118         }
119
120         if ok, err := onlyHaveInputActions(req); err != nil {
121                 return err
122         } else if ok {
123                 return errors.WithDetail(ErrBadActionConstruction, "transaction contains only input actions and no output actions")
124         }
125         return nil
126 }
127
128 func (a *API) mergeSpendActions(req *BuildRequest) ([]txbuilder.Action, error) {
129         actions := make([]txbuilder.Action, 0, len(req.Actions))
130         for i, act := range req.Actions {
131                 typ, ok := act["type"].(string)
132                 if !ok {
133                         return nil, errors.WithDetailf(ErrBadActionType, "no action type provided on action %d", i)
134                 }
135                 decoder, ok := a.actionDecoder(typ)
136                 if !ok {
137                         return nil, errors.WithDetailf(ErrBadActionType, "unknown action type %q on action %d", typ, i)
138                 }
139
140                 // Remarshal to JSON, the action may have been modified when we
141                 // filtered aliases.
142                 b, err := json.Marshal(act)
143                 if err != nil {
144                         return nil, err
145                 }
146                 action, err := decoder(b)
147                 if err != nil {
148                         return nil, errors.WithDetailf(ErrBadAction, "%s on action %d", err.Error(), i)
149                 }
150                 actions = append(actions, action)
151         }
152         actions = account.MergeSpendAction(actions)
153         return actions, nil
154 }
155
156 func (a *API) buildTxs(ctx context.Context, req *BuildRequest) ([]*txbuilder.Template, error) {
157         if err := a.checkRequestValidity(ctx, req); err != nil {
158                 return nil, err
159         }
160         actions, err := a.mergeSpendActions(req)
161         if err != nil {
162                 return nil, err
163         }
164
165         builder := txbuilder.NewBuilder(time.Now().Add(req.TTL.Duration))
166         tpls := []*txbuilder.Template{}
167         for _, action := range actions {
168                 if action.ActionType() == "spend_account" {
169                         tpls, err = account.SpendAccountChain(ctx, builder, action)
170                 } else {
171                         err = action.Build(ctx, builder)
172                 }
173
174                 if err != nil {
175                         builder.Rollback()
176                         return nil, err
177                 }
178         }
179
180         tpl, _, err := builder.Build()
181         if err != nil {
182                 builder.Rollback()
183                 return nil, err
184         }
185
186         tpls = append(tpls, tpl)
187         return tpls, nil
188 }
189
190 // POST /build-chain-transactions
191 func (a *API) buildChainTxs(ctx context.Context, buildReqs *BuildRequest) Response {
192         subctx := reqid.NewSubContext(ctx, reqid.New())
193         tmpls, err := a.buildTxs(subctx, buildReqs)
194         if err != nil {
195                 return NewErrorResponse(err)
196         }
197         return NewSuccessResponse(tmpls)
198 }
199
200 type submitTxResp struct {
201         TxID *bc.Hash `json:"tx_id"`
202 }
203
204 // POST /submit-transaction
205 func (a *API) submit(ctx context.Context, ins struct {
206         Tx types.Tx `json:"raw_transaction"`
207 }) Response {
208         if err := txbuilder.FinalizeTx(ctx, a.chain, &ins.Tx); err != nil {
209                 return NewErrorResponse(err)
210         }
211
212         log.WithField("tx_id", ins.Tx.ID.String()).Info("submit single tx")
213         return NewSuccessResponse(&submitTxResp{TxID: &ins.Tx.ID})
214 }
215
216 type submitTxsResp struct {
217         TxID []*bc.Hash `json:"tx_id"`
218 }
219
220 // POST /submit-transactions
221 func (a *API) submitTxs(ctx context.Context, ins struct {
222         Tx []types.Tx `json:"raw_transactions"`
223 }) Response {
224         txHashs := []*bc.Hash{}
225         for i := range ins.Tx {
226                 if err := txbuilder.FinalizeTx(ctx, a.chain, &ins.Tx[i]); err != nil {
227                         return NewErrorResponse(err)
228                 }
229                 log.WithField("tx_id", ins.Tx[i].ID.String()).Info("submit single tx")
230                 txHashs = append(txHashs, &ins.Tx[i].ID)
231         }
232         return NewSuccessResponse(&submitTxsResp{TxID: txHashs})
233 }
234
235 // EstimateTxGasResp estimate transaction consumed gas
236 type EstimateTxGasResp struct {
237         TotalNeu   int64 `json:"total_neu"`
238         StorageNeu int64 `json:"storage_neu"`
239         VMNeu      int64 `json:"vm_neu"`
240 }
241
242 // EstimateTxGas estimate consumed neu for transaction
243 func EstimateTxGas(template txbuilder.Template) (*EstimateTxGasResp, error) {
244         // base tx size and not include sign
245         data, err := template.Transaction.TxData.MarshalText()
246         if err != nil {
247                 return nil, err
248         }
249         baseTxSize := int64(len(data))
250
251         // extra tx size for sign witness parts
252         signSize := estimateSignSize(template.SigningInstructions)
253
254         // total gas for tx storage
255         totalTxSizeGas, ok := checked.MulInt64(baseTxSize+signSize, consensus.StorageGasRate)
256         if !ok {
257                 return nil, errors.New("calculate txsize gas got a math error")
258         }
259
260         // consume gas for run VM
261         totalP2WPKHGas := int64(0)
262         totalP2WSHGas := int64(0)
263         baseP2WPKHGas := int64(1419)
264         // flexible Gas is used for handle need extra utxo situation
265
266         for pos, inpID := range template.Transaction.Tx.InputIDs {
267                 sp, err := template.Transaction.Spend(inpID)
268                 if err != nil {
269                         continue
270                 }
271
272                 resOut, err := template.Transaction.Output(*sp.SpentOutputId)
273                 if err != nil {
274                         continue
275                 }
276
277                 if segwit.IsP2WPKHScript(resOut.ControlProgram.Code) {
278                         totalP2WPKHGas += baseP2WPKHGas
279                 } else if segwit.IsP2WSHScript(resOut.ControlProgram.Code) {
280                         sigInst := template.SigningInstructions[pos]
281                         totalP2WSHGas += estimateP2WSHGas(sigInst)
282                 }
283         }
284
285         // total estimate gas
286         totalGas := totalTxSizeGas + totalP2WPKHGas + totalP2WSHGas + flexibleGas
287
288         // rounding totalNeu with base rate 100000
289         totalNeu := float64(totalGas*consensus.VMGasRate) / defaultBaseRate
290         roundingNeu := math.Ceil(totalNeu)
291         estimateNeu := int64(roundingNeu) * int64(defaultBaseRate)
292
293         // TODO add priority
294
295         return &EstimateTxGasResp{
296                 TotalNeu:   estimateNeu,
297                 StorageNeu: totalTxSizeGas * consensus.VMGasRate,
298                 VMNeu:      (totalP2WPKHGas + totalP2WSHGas) * consensus.VMGasRate,
299         }, nil
300 }
301
302 // estimate p2wsh gas.
303 // OP_CHECKMULTISIG consume (984 * a - 72 * b - 63) gas,
304 // where a represent the num of public keys, and b represent the num of quorum.
305 func estimateP2WSHGas(sigInst *txbuilder.SigningInstruction) int64 {
306         P2WSHGas := int64(0)
307         baseP2WSHGas := int64(738)
308
309         for _, witness := range sigInst.WitnessComponents {
310                 switch t := witness.(type) {
311                 case *txbuilder.SignatureWitness:
312                         P2WSHGas += baseP2WSHGas + (984*int64(len(t.Keys)) - 72*int64(t.Quorum) - 63)
313                 case *txbuilder.RawTxSigWitness:
314                         P2WSHGas += baseP2WSHGas + (984*int64(len(t.Keys)) - 72*int64(t.Quorum) - 63)
315                 }
316         }
317         return P2WSHGas
318 }
319
320 // estimate signature part size.
321 // if need multi-sign, calculate the size according to the length of keys.
322 func estimateSignSize(signingInstructions []*txbuilder.SigningInstruction) int64 {
323         signSize := int64(0)
324         baseWitnessSize := int64(300)
325
326         for _, sigInst := range signingInstructions {
327                 for _, witness := range sigInst.WitnessComponents {
328                         switch t := witness.(type) {
329                         case *txbuilder.SignatureWitness:
330                                 signSize += int64(t.Quorum) * baseWitnessSize
331                         case *txbuilder.RawTxSigWitness:
332                                 signSize += int64(t.Quorum) * baseWitnessSize
333                         }
334                 }
335         }
336         return signSize
337 }
338
339 // POST /estimate-transaction-gas
340 func (a *API) estimateTxGas(ctx context.Context, in struct {
341         TxTemplate txbuilder.Template `json:"transaction_template"`
342 }) Response {
343         txGasResp, err := EstimateTxGas(in.TxTemplate)
344         if err != nil {
345                 return NewErrorResponse(err)
346         }
347         return NewSuccessResponse(txGasResp)
348 }