OSDN Git Service

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