OSDN Git Service

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