OSDN Git Service

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