OSDN Git Service

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