OSDN Git Service

0cb302877ba1dbbdf156d1ef7d7287971e5d2108
[bytom/bytom.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/bytom/account"
12         "github.com/bytom/blockchain/txbuilder"
13         "github.com/bytom/consensus"
14         "github.com/bytom/consensus/segwit"
15         "github.com/bytom/errors"
16         "github.com/bytom/net/http/reqid"
17         "github.com/bytom/protocol/bc"
18         "github.com/bytom/protocol/bc/types"
19         "github.com/bytom/protocol/vm/vmutil"
20 )
21
22 var (
23         defaultTxTTL    = 30 * time.Minute
24         defaultBaseRate = float64(100000)
25 )
26
27 func (a *API) actionDecoder(action string) (func([]byte) (txbuilder.Action, error), bool) {
28         decoders := map[string]func([]byte) (txbuilder.Action, error){
29                 "control_address":              txbuilder.DecodeControlAddressAction,
30                 "control_program":              txbuilder.DecodeControlProgramAction,
31                 "issue":                        a.wallet.AssetReg.DecodeIssueAction,
32                 "retire":                       txbuilder.DecodeRetireAction,
33                 "spend_account":                a.wallet.AccountMgr.DecodeSpendAction,
34                 "spend_account_unspent_output": a.wallet.AccountMgr.DecodeSpendUTXOAction,
35         }
36         decoder, ok := decoders[action]
37         return decoder, ok
38 }
39
40 func onlyHaveInputActions(req *BuildRequest) (bool, error) {
41         count := 0
42         for i, act := range req.Actions {
43                 actionType, ok := act["type"].(string)
44                 if !ok {
45                         return false, errors.WithDetailf(ErrBadActionType, "no action type provided on action %d", i)
46                 }
47
48                 if strings.HasPrefix(actionType, "spend") || actionType == "issue" {
49                         count++
50                 }
51         }
52
53         return count == len(req.Actions), nil
54 }
55
56 func (a *API) buildSingle(ctx context.Context, req *BuildRequest) (*txbuilder.Template, error) {
57         if err := a.checkRequestValidity(ctx, req); err != nil {
58                 return nil, err
59         }
60         actions, err := a.mergeSpendActions(req)
61         if err != nil {
62                 return nil, err
63         }
64
65         maxTime := time.Now().Add(req.TTL.Duration)
66         tpl, err := txbuilder.Build(ctx, req.Tx, actions, maxTime, req.TimeRange)
67         if errors.Root(err) == txbuilder.ErrAction {
68                 // append each of the inner errors contained in the data.
69                 var Errs string
70                 var rootErr error
71                 for i, innerErr := range errors.Data(err)["actions"].([]error) {
72                         if i == 0 {
73                                 rootErr = errors.Root(innerErr)
74                         }
75                         Errs = Errs + innerErr.Error()
76                 }
77                 err = errors.WithDetail(rootErr, Errs)
78         }
79         if err != nil {
80                 return nil, err
81         }
82
83         // ensure null is never returned for signing instructions
84         if tpl.SigningInstructions == nil {
85                 tpl.SigningInstructions = []*txbuilder.SigningInstruction{}
86         }
87         return tpl, nil
88 }
89
90 // POST /build-transaction
91 func (a *API) build(ctx context.Context, buildReqs *BuildRequest) Response {
92         subctx := reqid.NewSubContext(ctx, reqid.New())
93         tmpl, err := a.buildSingle(subctx, buildReqs)
94         if err != nil {
95                 return NewErrorResponse(err)
96         }
97
98         return NewSuccessResponse(tmpl)
99 }
100 func (a *API) checkRequestValidity(ctx context.Context, req *BuildRequest) error {
101         if err := a.completeMissingIDs(ctx, req); err != nil {
102                 return err
103         }
104
105         if req.TTL.Duration == 0 {
106                 req.TTL.Duration = defaultTxTTL
107         }
108
109         if ok, err := onlyHaveInputActions(req); err != nil {
110                 return err
111         } else if ok {
112                 return errors.WithDetail(ErrBadActionConstruction, "transaction contains only input actions and no output actions")
113         }
114         return nil
115 }
116
117 func (a *API) mergeSpendActions(req *BuildRequest) ([]txbuilder.Action, error) {
118         actions := make([]txbuilder.Action, 0, len(req.Actions))
119         for i, act := range req.Actions {
120                 typ, ok := act["type"].(string)
121                 if !ok {
122                         return nil, errors.WithDetailf(ErrBadActionType, "no action type provided on action %d", i)
123                 }
124                 decoder, ok := a.actionDecoder(typ)
125                 if !ok {
126                         return nil, errors.WithDetailf(ErrBadActionType, "unknown action type %q on action %d", typ, i)
127                 }
128
129                 // Remarshal to JSON, the action may have been modified when we
130                 // filtered aliases.
131                 b, err := json.Marshal(act)
132                 if err != nil {
133                         return nil, err
134                 }
135                 action, err := decoder(b)
136                 if err != nil {
137                         return nil, errors.WithDetailf(ErrBadAction, "%s on action %d", err.Error(), i)
138                 }
139                 actions = append(actions, action)
140         }
141         actions = account.MergeSpendAction(actions)
142         return actions, nil
143 }
144
145 func (a *API) buildTxs(ctx context.Context, req *BuildRequest) ([]*txbuilder.Template, error) {
146         if err := a.checkRequestValidity(ctx, req); err != nil {
147                 return nil, err
148         }
149         actions, err := a.mergeSpendActions(req)
150         if err != nil {
151                 return nil, err
152         }
153
154         builder := txbuilder.NewBuilder(time.Now().Add(req.TTL.Duration))
155         tpls := []*txbuilder.Template{}
156         for _, action := range actions {
157                 if action.ActionType() == "spend_account" {
158                         tpls, err = account.SpendAccountChain(ctx, builder, action)
159                 } else {
160                         err = action.Build(ctx, builder)
161                 }
162
163                 if err != nil {
164                         builder.Rollback()
165                         return nil, err
166                 }
167         }
168
169         tpl, _, err := builder.Build()
170         if err != nil {
171                 builder.Rollback()
172                 return nil, err
173         }
174
175         tpls = append(tpls, tpl)
176         return tpls, nil
177 }
178
179 // POST /build-chain-transactions
180 func (a *API) buildChainTxs(ctx context.Context, buildReqs *BuildRequest) Response {
181         subctx := reqid.NewSubContext(ctx, reqid.New())
182         tmpls, err := a.buildTxs(subctx, buildReqs)
183         if err != nil {
184                 return NewErrorResponse(err)
185         }
186         return NewSuccessResponse(tmpls)
187 }
188
189 type submitTxResp struct {
190         TxID *bc.Hash `json:"tx_id"`
191 }
192
193 // POST /submit-transaction
194 func (a *API) submit(ctx context.Context, ins struct {
195         Tx types.Tx `json:"raw_transaction"`
196 }) Response {
197         if err := txbuilder.FinalizeTx(ctx, a.chain, &ins.Tx); err != nil {
198                 return NewErrorResponse(err)
199         }
200
201         log.WithField("tx_id", ins.Tx.ID.String()).Info("submit single tx")
202         return NewSuccessResponse(&submitTxResp{TxID: &ins.Tx.ID})
203 }
204
205 type submitTxsResp struct {
206         TxID []*bc.Hash `json:"tx_id"`
207 }
208
209 // POST /submit-transactions
210 func (a *API) submitTxs(ctx context.Context, ins struct {
211         Tx []types.Tx `json:"raw_transactions"`
212 }) Response {
213         txHashs := []*bc.Hash{}
214         for i := range ins.Tx {
215                 if err := txbuilder.FinalizeTx(ctx, a.chain, &ins.Tx[i]); err != nil {
216                         return NewErrorResponse(err)
217                 }
218                 log.WithField("tx_id", ins.Tx[i].ID.String()).Info("submit single tx")
219                 txHashs = append(txHashs, &ins.Tx[i].ID)
220         }
221         return NewSuccessResponse(&submitTxsResp{TxID: txHashs})
222 }
223
224 // EstimateTxGasResp estimate transaction consumed gas
225 type EstimateTxGasResp struct {
226         TotalNeu    int64 `json:"total_neu"`
227         FlexibleNeu int64 `json:"flexible_neu"`
228         StorageNeu  int64 `json:"storage_neu"`
229         VMNeu       int64 `json:"vm_neu"`
230 }
231
232 // estimateTxGas estimate consumed neu for transaction
233 func estimateTxGas(template txbuilder.Template) (*EstimateTxGasResp, error) {
234         var baseP2WSHSize, totalWitnessSize, baseP2WSHGas, totalP2WPKHGas, totalP2WSHGas, totalIssueGas int64
235         baseSize := int64(176) // inputSize(112) + outputSize(64)
236         baseP2WPKHSize := int64(98)
237         baseP2WPKHGas := int64(1409)
238         for pos, input := range template.Transaction.TxData.Inputs {
239                 switch input.InputType() {
240                 case types.SpendInputType:
241                         controlProgram := input.ControlProgram()
242                         if segwit.IsP2WPKHScript(controlProgram) {
243                                 totalWitnessSize += baseP2WPKHSize
244                                 totalP2WPKHGas += baseP2WPKHGas
245                         } else if segwit.IsP2WSHScript(controlProgram) {
246                                 baseP2WSHSize, baseP2WSHGas = estimateP2WSHGas(template.SigningInstructions[pos])
247                                 totalWitnessSize += baseP2WSHSize
248                                 totalP2WSHGas += baseP2WSHGas
249                         }
250
251                 case types.IssuanceInputType:
252                         issuanceProgram := input.IssuanceProgram()
253                         if height, _ := vmutil.GetIssuanceProgramRestrictHeight(issuanceProgram); height > 0 {
254                                 // the gas for issue program with checking block height
255                                 totalIssueGas += 5
256                         }
257                         baseIssueSize, baseIssueGas := estimateIssueGas(template.SigningInstructions[pos])
258                         totalWitnessSize += baseIssueSize
259                         totalIssueGas += baseIssueGas
260                 }
261         }
262
263         flexibleGas := int64(0)
264         if totalP2WPKHGas > 0 {
265                 flexibleGas += baseP2WPKHGas + (baseSize+baseP2WPKHSize)*consensus.StorageGasRate
266         } else if totalP2WSHGas > 0 {
267                 flexibleGas += baseP2WSHGas + (baseSize+baseP2WSHSize)*consensus.StorageGasRate
268         } else if totalIssueGas > 0 {
269                 totalIssueGas += baseP2WPKHGas
270                 totalWitnessSize += baseSize + baseP2WPKHSize
271         }
272
273         // the total transaction storage gas
274         totalTxSizeGas := (int64(template.Transaction.TxData.SerializedSize) + totalWitnessSize) * consensus.StorageGasRate
275
276         // the total transaction gas is composed of storage and virtual machines
277         totalGas := totalTxSizeGas + totalP2WPKHGas + totalP2WSHGas + totalIssueGas + flexibleGas
278         return &EstimateTxGasResp{
279                 TotalNeu:    totalGas * consensus.VMGasRate,
280                 FlexibleNeu: flexibleGas * consensus.VMGasRate,
281                 StorageNeu:  totalTxSizeGas * consensus.VMGasRate,
282                 VMNeu:       (totalP2WPKHGas + totalP2WSHGas + totalIssueGas) * consensus.VMGasRate,
283         }, nil
284 }
285
286 // estimateP2WSH return the witness size and the gas consumed to execute the virtual machine for P2WSH program
287 func estimateP2WSHGas(sigInst *txbuilder.SigningInstruction) (int64, int64) {
288         var witnessSize, gas int64
289         for _, witness := range sigInst.WitnessComponents {
290                 switch t := witness.(type) {
291                 case *txbuilder.SignatureWitness:
292                         witnessSize += 33*int64(len(t.Keys)) + 65*int64(t.Quorum)
293                         gas += 1131*int64(len(t.Keys)) + 72*int64(t.Quorum) + 659
294                         if int64(len(t.Keys)) == 1 && int64(t.Quorum) == 1 {
295                                 gas += 27
296                         }
297                 case *txbuilder.RawTxSigWitness:
298                         witnessSize += 33*int64(len(t.Keys)) + 65*int64(t.Quorum)
299                         gas += 1131*int64(len(t.Keys)) + 72*int64(t.Quorum) + 659
300                         if int64(len(t.Keys)) == 1 && int64(t.Quorum) == 1 {
301                                 gas += 27
302                         }
303                 }
304         }
305         return witnessSize, gas
306 }
307
308 // estimateIssueGas return the witness size and the gas consumed to execute the virtual machine for issuance program
309 func estimateIssueGas(sigInst *txbuilder.SigningInstruction) (int64, int64) {
310         var witnessSize, gas int64
311         for _, witness := range sigInst.WitnessComponents {
312                 switch t := witness.(type) {
313                 case *txbuilder.SignatureWitness:
314                         witnessSize += 65 * int64(t.Quorum)
315                         gas += 1065*int64(len(t.Keys)) + 72*int64(t.Quorum) + 316
316                         if int64(len(t.Keys)) == 1 && int64(t.Quorum) == 1 {
317                                 gas += 27
318                         }
319                 case *txbuilder.RawTxSigWitness:
320                         witnessSize += 65 * int64(t.Quorum)
321                         gas += 1065*int64(len(t.Keys)) + 72*int64(t.Quorum) + 316
322                         if int64(len(t.Keys)) == 1 && int64(t.Quorum) == 1 {
323                                 gas += 27
324                         }
325                 }
326         }
327         return witnessSize, gas
328 }
329
330 // POST /estimate-transaction-gas
331 func (a *API) estimateTxGas(ctx context.Context, in struct {
332         TxTemplate txbuilder.Template `json:"transaction_template"`
333 }) Response {
334         txGasResp, err := estimateTxGas(in.TxTemplate)
335         if err != nil {
336                 return NewErrorResponse(err)
337         }
338         return NewSuccessResponse(txGasResp)
339 }