OSDN Git Service

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