OSDN Git Service

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