OSDN Git Service

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