OSDN Git Service

reorganize error code (#1133)
[bytom/bytom.git] / api / transact.go
1 package api
2
3 import (
4         "context"
5         "encoding/json"
6         "math"
7         "strings"
8         "time"
9
10         log "github.com/sirupsen/logrus"
11
12         "github.com/bytom/account"
13         "github.com/bytom/blockchain/txbuilder"
14         "github.com/bytom/consensus"
15         "github.com/bytom/consensus/segwit"
16         "github.com/bytom/errors"
17         "github.com/bytom/math/checked"
18         "github.com/bytom/net/http/reqid"
19         "github.com/bytom/protocol/bc"
20         "github.com/bytom/protocol/bc/types"
21 )
22
23 var (
24         defaultTxTTL    = 5 * time.Minute
25         defaultBaseRate = float64(100000)
26 )
27
28 func (a *API) actionDecoder(action string) (func([]byte) (txbuilder.Action, error), bool) {
29         decoders := map[string]func([]byte) (txbuilder.Action, error){
30                 "control_address":              txbuilder.DecodeControlAddressAction,
31                 "control_program":              txbuilder.DecodeControlProgramAction,
32                 "issue":                        a.wallet.AssetReg.DecodeIssueAction,
33                 "retire":                       txbuilder.DecodeRetireAction,
34                 "spend_account":                a.wallet.AccountMgr.DecodeSpendAction,
35                 "spend_account_unspent_output": a.wallet.AccountMgr.DecodeSpendUTXOAction,
36         }
37         decoder, ok := decoders[action]
38         return decoder, ok
39 }
40
41 func onlyHaveInputActions(req *BuildRequest) (bool, error) {
42         count := 0
43         for i, act := range req.Actions {
44                 actionType, ok := act["type"].(string)
45                 if !ok {
46                         return false, errors.WithDetailf(ErrBadActionType, "no action type provided on action %d", i)
47                 }
48
49                 if strings.HasPrefix(actionType, "spend") || actionType == "issue" {
50                         count++
51                 }
52         }
53
54         return count == len(req.Actions), nil
55 }
56
57 func (a *API) buildSingle(ctx context.Context, req *BuildRequest) (*txbuilder.Template, error) {
58         if err := a.completeMissingIDs(ctx, req); err != nil {
59                 return nil, err
60         }
61
62         if ok, err := onlyHaveInputActions(req); err != nil {
63                 return nil, err
64         } else if ok {
65                 return nil, errors.WithDetail(ErrBadActionConstruction, "transaction contains only input actions and no output actions")
66         }
67
68         actions := make([]txbuilder.Action, 0, len(req.Actions))
69         for i, act := range req.Actions {
70                 typ, ok := act["type"].(string)
71                 if !ok {
72                         return nil, errors.WithDetailf(ErrBadActionType, "no action type provided on action %d", i)
73                 }
74                 decoder, ok := a.actionDecoder(typ)
75                 if !ok {
76                         return nil, errors.WithDetailf(ErrBadActionType, "unknown action type %q on action %d", typ, i)
77                 }
78
79                 // Remarshal to JSON, the action may have been modified when we
80                 // filtered aliases.
81                 b, err := json.Marshal(act)
82                 if err != nil {
83                         return nil, err
84                 }
85                 action, err := decoder(b)
86                 if err != nil {
87                         return nil, errors.WithDetailf(ErrBadAction, "%s on action %d", err.Error(), i)
88                 }
89                 actions = append(actions, action)
90         }
91         actions = account.MergeSpendAction(actions)
92
93         ttl := req.TTL.Duration
94         if ttl == 0 {
95                 ttl = defaultTxTTL
96         }
97         maxTime := time.Now().Add(ttl)
98
99         tpl, err := txbuilder.Build(ctx, req.Tx, actions, maxTime, req.TimeRange)
100         if errors.Root(err) == txbuilder.ErrAction {
101                 // append each of the inner errors contained in the data.
102                 var Errs string
103                 var rootErr error
104                 for i, innerErr := range errors.Data(err)["actions"].([]error) {
105                         if i == 0 {
106                                 rootErr = errors.Root(innerErr)
107                         }
108                         Errs = Errs + innerErr.Error()
109                 }
110                 err = errors.WithDetail(rootErr, Errs)
111         }
112         if err != nil {
113                 return nil, err
114         }
115
116         // ensure null is never returned for signing instructions
117         if tpl.SigningInstructions == nil {
118                 tpl.SigningInstructions = []*txbuilder.SigningInstruction{}
119         }
120         return tpl, nil
121 }
122
123 // POST /build-transaction
124 func (a *API) build(ctx context.Context, buildReqs *BuildRequest) Response {
125         subctx := reqid.NewSubContext(ctx, reqid.New())
126
127         tmpl, err := a.buildSingle(subctx, buildReqs)
128         if err != nil {
129                 return NewErrorResponse(err)
130         }
131
132         return NewSuccessResponse(tmpl)
133 }
134
135 type submitTxResp struct {
136         TxID *bc.Hash `json:"tx_id"`
137 }
138
139 // POST /submit-transaction
140 func (a *API) submit(ctx context.Context, ins struct {
141         Tx types.Tx `json:"raw_transaction"`
142 }) Response {
143         if err := txbuilder.FinalizeTx(ctx, a.chain, &ins.Tx); err != nil {
144                 return NewErrorResponse(err)
145         }
146
147         log.WithField("tx_id", ins.Tx.ID.String()).Info("submit single tx")
148         return NewSuccessResponse(&submitTxResp{TxID: &ins.Tx.ID})
149 }
150
151 // EstimateTxGasResp estimate transaction consumed gas
152 type EstimateTxGasResp struct {
153         TotalNeu   int64 `json:"total_neu"`
154         StorageNeu int64 `json:"storage_neu"`
155         VMNeu      int64 `json:"vm_neu"`
156 }
157
158 // EstimateTxGas estimate consumed neu for transaction
159 func EstimateTxGas(template txbuilder.Template) (*EstimateTxGasResp, error) {
160         // base tx size and not include sign
161         data, err := template.Transaction.TxData.MarshalText()
162         if err != nil {
163                 return nil, err
164         }
165         baseTxSize := int64(len(data))
166
167         // extra tx size for sign witness parts
168         signSize := estimateSignSize(template.SigningInstructions)
169
170         // total gas for tx storage
171         totalTxSizeGas, ok := checked.MulInt64(baseTxSize+signSize, consensus.StorageGasRate)
172         if !ok {
173                 return nil, errors.New("calculate txsize gas got a math error")
174         }
175
176         // consume gas for run VM
177         totalP2WPKHGas := int64(0)
178         totalP2WSHGas := int64(0)
179         baseP2WPKHGas := int64(1419)
180
181         for pos, inpID := range template.Transaction.Tx.InputIDs {
182                 sp, err := template.Transaction.Spend(inpID)
183                 if err != nil {
184                         continue
185                 }
186
187                 resOut, err := template.Transaction.Output(*sp.SpentOutputId)
188                 if err != nil {
189                         continue
190                 }
191
192                 if segwit.IsP2WPKHScript(resOut.ControlProgram.Code) {
193                         totalP2WPKHGas += baseP2WPKHGas
194                 } else if segwit.IsP2WSHScript(resOut.ControlProgram.Code) {
195                         sigInst := template.SigningInstructions[pos]
196                         totalP2WSHGas += estimateP2WSHGas(sigInst)
197                 }
198         }
199
200         // total estimate gas
201         totalGas := totalTxSizeGas + totalP2WPKHGas + totalP2WSHGas
202
203         // rounding totalNeu with base rate 100000
204         totalNeu := float64(totalGas*consensus.VMGasRate) / defaultBaseRate
205         roundingNeu := math.Ceil(totalNeu)
206         estimateNeu := int64(roundingNeu) * int64(defaultBaseRate)
207
208         // TODO add priority
209
210         return &EstimateTxGasResp{
211                 TotalNeu:   estimateNeu,
212                 StorageNeu: totalTxSizeGas * consensus.VMGasRate,
213                 VMNeu:      (totalP2WPKHGas + totalP2WSHGas) * consensus.VMGasRate,
214         }, nil
215 }
216
217 // estimate p2wsh gas.
218 // OP_CHECKMULTISIG consume (984 * a - 72 * b - 63) gas,
219 // where a represent the num of public keys, and b represent the num of quorum.
220 func estimateP2WSHGas(sigInst *txbuilder.SigningInstruction) int64 {
221         P2WSHGas := int64(0)
222         baseP2WSHGas := int64(738)
223
224         for _, witness := range sigInst.WitnessComponents {
225                 switch t := witness.(type) {
226                 case *txbuilder.SignatureWitness:
227                         P2WSHGas += baseP2WSHGas + (984*int64(len(t.Keys)) - 72*int64(t.Quorum) - 63)
228                 case *txbuilder.RawTxSigWitness:
229                         P2WSHGas += baseP2WSHGas + (984*int64(len(t.Keys)) - 72*int64(t.Quorum) - 63)
230                 }
231         }
232         return P2WSHGas
233 }
234
235 // estimate signature part size.
236 // if need multi-sign, calculate the size according to the length of keys.
237 func estimateSignSize(signingInstructions []*txbuilder.SigningInstruction) int64 {
238         signSize := int64(0)
239         baseWitnessSize := int64(300)
240
241         for _, sigInst := range signingInstructions {
242                 for _, witness := range sigInst.WitnessComponents {
243                         switch t := witness.(type) {
244                         case *txbuilder.SignatureWitness:
245                                 signSize += int64(t.Quorum) * baseWitnessSize
246                         case *txbuilder.RawTxSigWitness:
247                                 signSize += int64(t.Quorum) * baseWitnessSize
248                         }
249                 }
250         }
251         return signSize
252 }
253
254 // POST /estimate-transaction-gas
255 func (a *API) estimateTxGas(ctx context.Context, in struct {
256         TxTemplate txbuilder.Template `json:"transaction_template"`
257 }) Response {
258         txGasResp, err := EstimateTxGas(in.TxTemplate)
259         if err != nil {
260                 return NewErrorResponse(err)
261         }
262         return NewSuccessResponse(txGasResp)
263 }