OSDN Git Service

Merge pull request #686 from Bytom/dev-gas
[bytom/bytom.git] / api / transact.go
1 package api
2
3 import (
4         "context"
5         "encoding/json"
6         "fmt"
7         "strings"
8         "time"
9
10         log "github.com/sirupsen/logrus"
11
12         "github.com/bytom/blockchain/pseudohsm"
13         "github.com/bytom/blockchain/txbuilder"
14         "github.com/bytom/consensus"
15         "github.com/bytom/errors"
16         "github.com/bytom/math/checked"
17         "github.com/bytom/net/http/reqid"
18         "github.com/bytom/protocol/bc"
19         "github.com/bytom/protocol/bc/types"
20 )
21
22 var defaultTxTTL = 5 * time.Minute
23
24 func (a *API) actionDecoder(action string) (func([]byte) (txbuilder.Action, error), bool) {
25         var decoder func([]byte) (txbuilder.Action, error)
26         switch action {
27         case "control_address":
28                 decoder = txbuilder.DecodeControlAddressAction
29         case "control_program":
30                 decoder = txbuilder.DecodeControlProgramAction
31         case "control_receiver":
32                 decoder = txbuilder.DecodeControlReceiverAction
33         case "issue":
34                 decoder = a.wallet.AssetReg.DecodeIssueAction
35         case "retire":
36                 decoder = txbuilder.DecodeRetireAction
37         case "spend_account":
38                 decoder = a.wallet.AccountMgr.DecodeSpendAction
39         case "spend_account_unspent_output":
40                 decoder = a.wallet.AccountMgr.DecodeSpendUTXOAction
41         default:
42                 return nil, false
43         }
44         return decoder, true
45 }
46
47 func mergeActions(req *BuildRequest) []map[string]interface{} {
48         var actions []map[string]interface{}
49         actionMap := make(map[string]map[string]interface{})
50
51         for _, m := range req.Actions {
52                 if actionType := m["type"].(string); actionType != "spend_account" {
53                         actions = append(actions, m)
54                         continue
55                 }
56
57                 actionKey := m["asset_id"].(string) + m["account_id"].(string)
58                 amountNumber := m["amount"].(json.Number)
59                 amount, _ := amountNumber.Int64()
60
61                 if tmpM, ok := actionMap[actionKey]; ok {
62                         tmpNumber, _ := tmpM["amount"].(json.Number)
63                         tmpAmount, _ := tmpNumber.Int64()
64                         tmpM["amount"] = json.Number(fmt.Sprintf("%v", tmpAmount+amount))
65                 } else {
66                         actionMap[actionKey] = m
67                         actions = append(actions, m)
68                 }
69         }
70
71         return actions
72 }
73
74 func onlyHaveSpendActions(req *BuildRequest) bool {
75         count := 0
76         for _, m := range req.Actions {
77                 if actionType := m["type"].(string); strings.HasPrefix(actionType, "spend") {
78                         count++
79                 }
80         }
81
82         return count == len(req.Actions)
83 }
84
85 func (a *API) buildSingle(ctx context.Context, req *BuildRequest) (*txbuilder.Template, error) {
86         err := a.filterAliases(ctx, req)
87         if err != nil {
88                 return nil, err
89         }
90
91         if onlyHaveSpendActions(req) {
92                 return nil, errors.New("transaction only contain spend actions, didn't have output actions")
93         }
94
95         reqActions := mergeActions(req)
96         actions := make([]txbuilder.Action, 0, len(reqActions))
97         for i, act := range reqActions {
98                 typ, ok := act["type"].(string)
99                 if !ok {
100                         return nil, errors.WithDetailf(errBadActionType, "no action type provided on action %d", i)
101                 }
102                 decoder, ok := a.actionDecoder(typ)
103                 if !ok {
104                         return nil, errors.WithDetailf(errBadActionType, "unknown action type %q on action %d", typ, i)
105                 }
106
107                 // Remarshal to JSON, the action may have been modified when we
108                 // filtered aliases.
109                 b, err := json.Marshal(act)
110                 if err != nil {
111                         return nil, err
112                 }
113                 action, err := decoder(b)
114                 if err != nil {
115                         return nil, errors.WithDetailf(errBadAction, "%s on action %d", err.Error(), i)
116                 }
117                 actions = append(actions, action)
118         }
119
120         ttl := req.TTL.Duration
121         if ttl == 0 {
122                 ttl = defaultTxTTL
123         }
124         maxTime := time.Now().Add(ttl)
125
126         tpl, err := txbuilder.Build(ctx, req.Tx, actions, maxTime, req.TimeRange)
127         if errors.Root(err) == txbuilder.ErrAction {
128                 // append each of the inner errors contained in the data.
129                 var Errs string
130                 for _, innerErr := range errors.Data(err)["actions"].([]error) {
131                         Errs = Errs + "<" + innerErr.Error() + ">"
132                 }
133                 err = errors.New(err.Error() + "-" + Errs)
134         }
135         if err != nil {
136                 return nil, err
137         }
138
139         // ensure null is never returned for signing instructions
140         if tpl.SigningInstructions == nil {
141                 tpl.SigningInstructions = []*txbuilder.SigningInstruction{}
142         }
143         return tpl, nil
144 }
145
146 // POST /build-transaction
147 func (a *API) build(ctx context.Context, buildReqs *BuildRequest) Response {
148         subctx := reqid.NewSubContext(ctx, reqid.New())
149
150         tmpl, err := a.buildSingle(subctx, buildReqs)
151         if err != nil {
152                 return NewErrorResponse(err)
153         }
154
155         return NewSuccessResponse(tmpl)
156 }
157
158 func (a *API) submitSingle(ctx context.Context, tpl *txbuilder.Template) (map[string]string, error) {
159         if tpl.Transaction == nil {
160                 return nil, errors.Wrap(txbuilder.ErrMissingRawTx)
161         }
162
163         if err := txbuilder.FinalizeTx(ctx, a.chain, tpl.Transaction); err != nil {
164                 return nil, errors.Wrapf(err, "tx %s", tpl.Transaction.ID.String())
165         }
166
167         return map[string]string{"tx_id": tpl.Transaction.ID.String()}, nil
168 }
169
170 type submitTxResp struct {
171         TxID *bc.Hash `json:"tx_id"`
172 }
173
174 // POST /submit-transaction
175 func (a *API) submit(ctx context.Context, ins struct {
176         Tx types.Tx `json:"raw_transaction"`
177 }) Response {
178         if err := txbuilder.FinalizeTx(ctx, a.chain, &ins.Tx); err != nil {
179                 return NewErrorResponse(err)
180         }
181
182         log.WithField("tx_id", ins.Tx.ID).Info("submit single tx")
183         return NewSuccessResponse(&submitTxResp{TxID: &ins.Tx.ID})
184 }
185
186 // POST /sign-submit-transaction
187 func (a *API) signSubmit(ctx context.Context, x struct {
188         Password string             `json:"password"`
189         Txs      txbuilder.Template `json:"transaction"`
190 }) Response {
191         if err := txbuilder.Sign(ctx, &x.Txs, nil, x.Password, a.pseudohsmSignTemplate); err != nil {
192                 log.WithField("build err", err).Error("fail on sign transaction.")
193                 return NewErrorResponse(err)
194         }
195
196         if signCount, complete := txbuilder.SignInfo(&x.Txs); !complete && signCount == 0 {
197                 return NewErrorResponse(pseudohsm.ErrLoadKey)
198         }
199         log.Info("Sign Transaction complete.")
200
201         txID, err := a.submitSingle(nil, &x.Txs)
202         if err != nil {
203                 log.WithField("err", err).Error("submit single tx")
204                 return NewErrorResponse(err)
205         }
206
207         log.WithField("tx_id", txID["tx_id"]).Info("submit single tx")
208         return NewSuccessResponse(txID)
209 }
210
211 type EstimateTxGasResp struct {
212         LeftBTM     int64 `json:"left_btm"`
213         ConsumedBTM int64 `json:"consumed_btm"`
214         LeftGas     int64 `json:"left_gas"`
215         ConsumedGas int64 `json:"consumed_gas"`
216         StorageGas  int64 `json:"storage_gas"`
217         VMGas       int64 `json:"vm_gas"`
218 }
219
220 // POST /estimate-transaction-gas
221 func (a *API) estimateTxGas(ctx context.Context, ins struct {
222         Tx types.Tx `json:"raw_transaction"`
223 }) Response {
224         gasState, err := txbuilder.EstimateTxGas(a.chain, &ins.Tx)
225         if err != nil {
226                 return NewErrorResponse(err)
227         }
228
229         btmLeft, ok := checked.MulInt64(gasState.GasLeft, consensus.VMGasRate)
230         if !ok {
231                 return NewErrorResponse(errors.New("calculate btmleft got a math error"))
232         }
233
234         txGasResp := &EstimateTxGasResp{
235                 LeftBTM:     btmLeft,
236                 ConsumedBTM: int64(gasState.BTMValue) - btmLeft,
237                 LeftGas:     gasState.GasLeft,
238                 ConsumedGas: gasState.GasUsed,
239                 StorageGas:  gasState.StorageGas,
240                 VMGas:       gasState.GasUsed - gasState.StorageGas,
241         }
242
243         return NewSuccessResponse(txGasResp)
244 }