OSDN Git Service

bdd8b16e88aef53cff89beb1d5c1a65807e261bc
[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         var decoder func([]byte) (txbuilder.Action, error)
30         switch action {
31         case "control_address":
32                 decoder = txbuilder.DecodeControlAddressAction
33         case "control_program":
34                 decoder = txbuilder.DecodeControlProgramAction
35         case "control_receiver":
36                 decoder = txbuilder.DecodeControlReceiverAction
37         case "issue":
38                 decoder = a.wallet.AssetReg.DecodeIssueAction
39         case "retire":
40                 decoder = txbuilder.DecodeRetireAction
41         case "spend_account":
42                 decoder = a.wallet.AccountMgr.DecodeSpendAction
43         case "spend_account_unspent_output":
44                 decoder = a.wallet.AccountMgr.DecodeSpendUTXOAction
45         default:
46                 return nil, false
47         }
48         return decoder, true
49 }
50
51 func onlyHaveSpendActions(req *BuildRequest) bool {
52         count := 0
53         for _, m := range req.Actions {
54                 if actionType := m["type"].(string); strings.HasPrefix(actionType, "spend") {
55                         count++
56                 }
57         }
58
59         return count == len(req.Actions)
60 }
61
62 func (a *API) buildSingle(ctx context.Context, req *BuildRequest) (*txbuilder.Template, error) {
63         if err := a.completeMissingIds(ctx, req); err != nil {
64                 return nil, err
65         }
66
67         if onlyHaveSpendActions(req) {
68                 return nil, errors.New("transaction only contain spend actions, didn't have output actions")
69         }
70
71         spendActions := []txbuilder.Action{}
72         actions := make([]txbuilder.Action, 0, len(req.Actions))
73         for i, act := range req.Actions {
74                 typ, ok := act["type"].(string)
75                 if !ok {
76                         return nil, errors.WithDetailf(errBadActionType, "no action type provided on action %d", i)
77                 }
78                 decoder, ok := a.actionDecoder(typ)
79                 if !ok {
80                         return nil, errors.WithDetailf(errBadActionType, "unknown action type %q on action %d", typ, i)
81                 }
82
83                 // Remarshal to JSON, the action may have been modified when we
84                 // filtered aliases.
85                 b, err := json.Marshal(act)
86                 if err != nil {
87                         return nil, err
88                 }
89                 action, err := decoder(b)
90                 if err != nil {
91                         return nil, errors.WithDetailf(errBadAction, "%s on action %d", err.Error(), i)
92                 }
93
94                 if typ == "spend_account" {
95                         spendActions = append(spendActions, action)
96                 } else {
97                         actions = append(actions, action)
98                 }
99         }
100         actions = append(account.MergeSpendAction(spendActions), actions...)
101
102         ttl := req.TTL.Duration
103         if ttl == 0 {
104                 ttl = defaultTxTTL
105         }
106         maxTime := time.Now().Add(ttl)
107
108         tpl, err := txbuilder.Build(ctx, req.Tx, actions, maxTime, req.TimeRange)
109         if errors.Root(err) == txbuilder.ErrAction {
110                 // append each of the inner errors contained in the data.
111                 var Errs string
112                 for _, innerErr := range errors.Data(err)["actions"].([]error) {
113                         Errs = Errs + "<" + innerErr.Error() + ">"
114                 }
115                 err = errors.New(err.Error() + "-" + Errs)
116         }
117         if err != nil {
118                 return nil, err
119         }
120
121         // ensure null is never returned for signing instructions
122         if tpl.SigningInstructions == nil {
123                 tpl.SigningInstructions = []*txbuilder.SigningInstruction{}
124         }
125         return tpl, nil
126 }
127
128 // POST /build-transaction
129 func (a *API) build(ctx context.Context, buildReqs *BuildRequest) Response {
130         subctx := reqid.NewSubContext(ctx, reqid.New())
131
132         tmpl, err := a.buildSingle(subctx, buildReqs)
133         if err != nil {
134                 return NewErrorResponse(err)
135         }
136
137         return NewSuccessResponse(tmpl)
138 }
139
140 type submitTxResp struct {
141         TxID *bc.Hash `json:"tx_id"`
142 }
143
144 // POST /submit-transaction
145 func (a *API) submit(ctx context.Context, ins struct {
146         Tx types.Tx `json:"raw_transaction"`
147 }) Response {
148         if err := txbuilder.FinalizeTx(ctx, a.chain, &ins.Tx); err != nil {
149                 return NewErrorResponse(err)
150         }
151
152         log.WithField("tx_id", ins.Tx.ID.String()).Info("submit single tx")
153         return NewSuccessResponse(&submitTxResp{TxID: &ins.Tx.ID})
154 }
155
156 // EstimateTxGasResp estimate transaction consumed gas
157 type EstimateTxGasResp struct {
158         TotalNeu   int64 `json:"total_neu"`
159         StorageNeu int64 `json:"storage_neu"`
160         VMNeu      int64 `json:"vm_neu"`
161 }
162
163 // EstimateTxGas estimate consumed neu for transaction
164 func EstimateTxGas(template txbuilder.Template) (*EstimateTxGasResp, error) {
165         // base tx size and not include sign
166         data, err := template.Transaction.TxData.MarshalText()
167         if err != nil {
168                 return nil, err
169         }
170         baseTxSize := int64(len(data))
171
172         // extra tx size for sign witness parts
173         baseWitnessSize := int64(300)
174         lenSignInst := int64(len(template.SigningInstructions))
175         signSize := baseWitnessSize * lenSignInst
176
177         // total gas for tx storage
178         totalTxSizeGas, ok := checked.MulInt64(baseTxSize+signSize, consensus.StorageGasRate)
179         if !ok {
180                 return nil, errors.New("calculate txsize gas got a math error")
181         }
182
183         // consume gas for run VM
184         totalP2WPKHGas := int64(0)
185         totalP2WSHGas := int64(0)
186         baseP2WPKHGas := int64(1419)
187         baseP2WSHGas := int64(2499)
188
189         for _, inpID := range template.Transaction.Tx.InputIDs {
190                 sp, err := template.Transaction.Spend(inpID)
191                 if err != nil {
192                         continue
193                 }
194
195                 resOut, err := template.Transaction.Output(*sp.SpentOutputId)
196                 if err != nil {
197                         continue
198                 }
199
200                 if segwit.IsP2WPKHScript(resOut.ControlProgram.Code) {
201                         totalP2WPKHGas += baseP2WPKHGas
202                 } else if segwit.IsP2WSHScript(resOut.ControlProgram.Code) {
203                         totalP2WSHGas += baseP2WSHGas
204                 }
205         }
206
207         // total estimate gas
208         totalGas := totalTxSizeGas + totalP2WPKHGas + totalP2WSHGas
209
210         // rounding totalNeu with base rate 100000
211         totalNeu := float64(totalGas*consensus.VMGasRate) / defaultBaseRate
212         roundingNeu := math.Ceil(totalNeu)
213         estimateNeu := int64(roundingNeu) * int64(defaultBaseRate)
214
215         return &EstimateTxGasResp{
216                 TotalNeu:   estimateNeu,
217                 StorageNeu: totalTxSizeGas * consensus.VMGasRate,
218                 VMNeu:      (totalP2WPKHGas + totalP2WSHGas) * consensus.VMGasRate,
219         }, nil
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 := EstimateTxGas(in.TxTemplate)
227         if err != nil {
228                 return NewErrorResponse(err)
229         }
230         return NewSuccessResponse(txGasResp)
231 }