OSDN Git Service

87a75e294bd97c0955f20131871e58707984bfc5
[bytom/vapor.git] / api / transact.go
1 package api
2
3 import (
4         "context"
5         "encoding/json"
6         "strings"
7         "time"
8
9         log "github.com/sirupsen/logrus"
10
11         "github.com/vapor/account"
12         "github.com/vapor/blockchain/txbuilder"
13         "github.com/vapor/errors"
14         "github.com/vapor/net/http/reqid"
15         "github.com/vapor/protocol/bc"
16         "github.com/vapor/protocol/bc/types"
17 )
18
19 var (
20         defaultTxTTL    = 30 * time.Minute
21         defaultBaseRate = float64(100000)
22 )
23
24 func (a *API) actionDecoder(action string) (func([]byte) (txbuilder.Action, error), bool) {
25         decoders := map[string]func([]byte) (txbuilder.Action, error){
26                 "control_address":              txbuilder.DecodeControlAddressAction,
27                 "control_program":              txbuilder.DecodeControlProgramAction,
28                 "issue":                        a.wallet.AssetReg.DecodeIssueAction,
29                 "retire":                       txbuilder.DecodeRetireAction,
30                 "spend_account":                a.wallet.AccountMgr.DecodeSpendAction,
31                 "spend_account_unspent_output": a.wallet.AccountMgr.DecodeSpendUTXOAction,
32         }
33         decoder, ok := decoders[action]
34         return decoder, ok
35 }
36
37 func onlyHaveInputActions(req *BuildRequest) (bool, error) {
38         count := 0
39         for i, act := range req.Actions {
40                 actionType, ok := act["type"].(string)
41                 if !ok {
42                         return false, errors.WithDetailf(ErrBadActionType, "no action type provided on action %d", i)
43                 }
44
45                 if strings.HasPrefix(actionType, "spend") || actionType == "issue" {
46                         count++
47                 }
48         }
49
50         return count == len(req.Actions), nil
51 }
52
53 func (a *API) buildSingle(ctx context.Context, req *BuildRequest) (*txbuilder.Template, error) {
54         if err := a.checkRequestValidity(ctx, req); err != nil {
55                 return nil, err
56         }
57         actions, err := a.mergeSpendActions(req)
58         if err != nil {
59                 return nil, err
60         }
61
62         maxTime := time.Now().Add(req.TTL.Duration)
63         tpl, err := txbuilder.Build(ctx, req.Tx, actions, maxTime, req.TimeRange)
64         if errors.Root(err) == txbuilder.ErrAction {
65                 // append each of the inner errors contained in the data.
66                 var Errs string
67                 var rootErr error
68                 for i, innerErr := range errors.Data(err)["actions"].([]error) {
69                         if i == 0 {
70                                 rootErr = errors.Root(innerErr)
71                         }
72                         Errs = Errs + innerErr.Error()
73                 }
74                 err = errors.WithDetail(rootErr, Errs)
75         }
76         if err != nil {
77                 return nil, err
78         }
79
80         // ensure null is never returned for signing instructions
81         if tpl.SigningInstructions == nil {
82                 tpl.SigningInstructions = []*txbuilder.SigningInstruction{}
83         }
84         return tpl, nil
85 }
86
87 // POST /build-transaction
88 func (a *API) build(ctx context.Context, buildReqs *BuildRequest) Response {
89         subctx := reqid.NewSubContext(ctx, reqid.New())
90         tmpl, err := a.buildSingle(subctx, buildReqs)
91         if err != nil {
92                 return NewErrorResponse(err)
93         }
94
95         return NewSuccessResponse(tmpl)
96 }
97 func (a *API) checkRequestValidity(ctx context.Context, req *BuildRequest) error {
98         if err := a.completeMissingIDs(ctx, req); err != nil {
99                 return err
100         }
101
102         if req.TTL.Duration == 0 {
103                 req.TTL.Duration = defaultTxTTL
104         }
105
106         if ok, err := onlyHaveInputActions(req); err != nil {
107                 return err
108         } else if ok {
109                 return errors.WithDetail(ErrBadActionConstruction, "transaction contains only input actions and no output actions")
110         }
111         return nil
112 }
113
114 func (a *API) mergeSpendActions(req *BuildRequest) ([]txbuilder.Action, error) {
115         actions := make([]txbuilder.Action, 0, len(req.Actions))
116         for i, act := range req.Actions {
117                 typ, ok := act["type"].(string)
118                 if !ok {
119                         return nil, errors.WithDetailf(ErrBadActionType, "no action type provided on action %d", i)
120                 }
121                 decoder, ok := a.actionDecoder(typ)
122                 if !ok {
123                         return nil, errors.WithDetailf(ErrBadActionType, "unknown action type %q on action %d", typ, i)
124                 }
125
126                 // Remarshal to JSON, the action may have been modified when we
127                 // filtered aliases.
128                 b, err := json.Marshal(act)
129                 if err != nil {
130                         return nil, err
131                 }
132                 action, err := decoder(b)
133                 if err != nil {
134                         return nil, errors.WithDetailf(ErrBadAction, "%s on action %d", err.Error(), i)
135                 }
136                 actions = append(actions, action)
137         }
138         actions = account.MergeSpendAction(actions)
139         return actions, nil
140 }
141
142 func (a *API) buildTxs(ctx context.Context, req *BuildRequest) ([]*txbuilder.Template, error) {
143         if err := a.checkRequestValidity(ctx, req); err != nil {
144                 return nil, err
145         }
146         actions, err := a.mergeSpendActions(req)
147         if err != nil {
148                 return nil, err
149         }
150
151         builder := txbuilder.NewBuilder(time.Now().Add(req.TTL.Duration))
152         tpls := []*txbuilder.Template{}
153         for _, action := range actions {
154                 if action.ActionType() == "spend_account" {
155                         tpls, err = account.SpendAccountChain(ctx, builder, action)
156                 } else {
157                         err = action.Build(ctx, builder)
158                 }
159
160                 if err != nil {
161                         builder.Rollback()
162                         return nil, err
163                 }
164         }
165
166         tpl, _, err := builder.Build()
167         if err != nil {
168                 builder.Rollback()
169                 return nil, err
170         }
171
172         tpls = append(tpls, tpl)
173         return tpls, nil
174 }
175
176 // POST /build-chain-transactions
177 func (a *API) buildChainTxs(ctx context.Context, buildReqs *BuildRequest) Response {
178         subctx := reqid.NewSubContext(ctx, reqid.New())
179         tmpls, err := a.buildTxs(subctx, buildReqs)
180         if err != nil {
181                 return NewErrorResponse(err)
182         }
183         return NewSuccessResponse(tmpls)
184 }
185
186 type submitTxResp struct {
187         TxID *bc.Hash `json:"tx_id"`
188 }
189
190 // POST /submit-transaction
191 func (a *API) submit(ctx context.Context, ins struct {
192         Tx types.Tx `json:"raw_transaction"`
193 }) Response {
194         if err := txbuilder.FinalizeTx(ctx, a.chain, &ins.Tx); err != nil {
195                 return NewErrorResponse(err)
196         }
197
198         log.WithField("tx_id", ins.Tx.ID.String()).Info("submit single tx")
199         return NewSuccessResponse(&submitTxResp{TxID: &ins.Tx.ID})
200 }
201
202 type submitTxsResp struct {
203         TxID []*bc.Hash `json:"tx_id"`
204 }
205
206 // POST /submit-transactions
207 func (a *API) submitTxs(ctx context.Context, ins struct {
208         Tx []types.Tx `json:"raw_transactions"`
209 }) Response {
210         txHashs := []*bc.Hash{}
211         for i := range ins.Tx {
212                 if err := txbuilder.FinalizeTx(ctx, a.chain, &ins.Tx[i]); err != nil {
213                         return NewErrorResponse(err)
214                 }
215                 log.WithField("tx_id", ins.Tx[i].ID.String()).Info("submit single tx")
216                 txHashs = append(txHashs, &ins.Tx[i].ID)
217         }
218         return NewSuccessResponse(&submitTxsResp{TxID: txHashs})
219 }
220
221 // POST /estimate-transaction-gas
222 func (a *API) estimateTxGas(ctx context.Context, in struct {
223         TxTemplate txbuilder.Template `json:"transaction_template"`
224 }) Response {
225         txGasResp, err := txbuilder.EstimateTxGas(in.TxTemplate)
226         if err != nil {
227                 return NewErrorResponse(err)
228         }
229         return NewSuccessResponse(txGasResp)
230 }