OSDN Git Service

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