OSDN Git Service

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