11 log "github.com/sirupsen/logrus"
13 "github.com/bytom/account"
14 "github.com/bytom/blockchain/txbuilder"
15 "github.com/bytom/consensus"
16 "github.com/bytom/consensus/segwit"
17 "github.com/bytom/errors"
18 "github.com/bytom/math/checked"
19 "github.com/bytom/net/http/reqid"
20 "github.com/bytom/protocol/bc"
21 "github.com/bytom/protocol/bc/types"
25 defaultTxTTL = 5 * time.Minute
26 defaultBaseRate = float64(100000)
29 func (a *API) actionDecoder(action string) (func([]byte) (txbuilder.Action, error), bool) {
30 var decoder func([]byte) (txbuilder.Action, error)
32 case "control_address":
33 decoder = txbuilder.DecodeControlAddressAction
34 case "control_program":
35 decoder = txbuilder.DecodeControlProgramAction
36 case "control_receiver":
37 decoder = txbuilder.DecodeControlReceiverAction
39 decoder = a.wallet.AssetReg.DecodeIssueAction
41 decoder = txbuilder.DecodeRetireAction
43 decoder = a.wallet.AccountMgr.DecodeSpendAction
44 case "spend_account_unspent_output":
45 decoder = a.wallet.AccountMgr.DecodeSpendUTXOAction
52 // TODO modify mergeActions to loadSpendAction
53 func mergeActions(req *BuildRequest) ([]map[string]interface{}, error) {
54 var actions []map[string]interface{}
55 actionMap := make(map[string]map[string]interface{})
57 for _, m := range req.Actions {
58 if actionType := m["type"].(string); actionType != "spend_account" {
59 actions = append(actions, m)
63 if m["amount"] == nil {
64 return nil, errEmptyAmount
67 amountNumber := m["amount"].(json.Number)
68 amount, err := amountNumber.Int64()
69 if err != nil || amount == 0 {
70 return nil, errBadAmount
73 actionKey := m["asset_id"].(string) + m["account_id"].(string)
74 if tmpM, ok := actionMap[actionKey]; ok {
75 if tmpM["amount"] == nil {
76 return nil, errEmptyAmount
79 tmpNumber := tmpM["amount"].(json.Number)
80 tmpAmount, err := tmpNumber.Int64()
81 if err != nil || tmpAmount == 0 {
82 return nil, errBadAmount
85 tmpM["amount"] = json.Number(fmt.Sprintf("%v", tmpAmount+amount))
87 actionMap[actionKey] = m
88 actions = append(actions, m)
95 func onlyHaveSpendActions(req *BuildRequest) bool {
97 for _, m := range req.Actions {
98 if actionType := m["type"].(string); strings.HasPrefix(actionType, "spend") {
103 return count == len(req.Actions)
106 func (a *API) buildSingle(ctx context.Context, req *BuildRequest) (*txbuilder.Template, error) {
107 err := a.filterAliases(ctx, req)
112 if onlyHaveSpendActions(req) {
113 return nil, errors.New("transaction only contain spend actions, didn't have output actions")
116 spendActions := []txbuilder.Action{}
117 actions := make([]txbuilder.Action, 0, len(req.Actions))
118 for i, act := range req.Actions {
119 typ, ok := act["type"].(string)
121 return nil, errors.WithDetailf(errBadActionType, "no action type provided on action %d", i)
123 decoder, ok := a.actionDecoder(typ)
125 return nil, errors.WithDetailf(errBadActionType, "unknown action type %q on action %d", typ, i)
128 // Remarshal to JSON, the action may have been modified when we
130 b, err := json.Marshal(act)
134 action, err := decoder(b)
136 return nil, errors.WithDetailf(errBadAction, "%s on action %d", err.Error(), i)
139 if typ == "spend_account" {
140 spendActions = append(spendActions, action)
142 actions = append(actions, action)
145 actions = append(account.MergeSpendAction(spendActions), actions...)
147 ttl := req.TTL.Duration
151 maxTime := time.Now().Add(ttl)
153 tpl, err := txbuilder.Build(ctx, req.Tx, actions, maxTime, req.TimeRange)
154 if errors.Root(err) == txbuilder.ErrAction {
155 // append each of the inner errors contained in the data.
157 for _, innerErr := range errors.Data(err)["actions"].([]error) {
158 Errs = Errs + "<" + innerErr.Error() + ">"
160 err = errors.New(err.Error() + "-" + Errs)
166 // ensure null is never returned for signing instructions
167 if tpl.SigningInstructions == nil {
168 tpl.SigningInstructions = []*txbuilder.SigningInstruction{}
173 // POST /build-transaction
174 func (a *API) build(ctx context.Context, buildReqs *BuildRequest) Response {
175 subctx := reqid.NewSubContext(ctx, reqid.New())
177 tmpl, err := a.buildSingle(subctx, buildReqs)
179 return NewErrorResponse(err)
182 return NewSuccessResponse(tmpl)
185 type submitTxResp struct {
186 TxID *bc.Hash `json:"tx_id"`
189 // POST /submit-transaction
190 func (a *API) submit(ctx context.Context, ins struct {
191 Tx types.Tx `json:"raw_transaction"`
193 if err := txbuilder.FinalizeTx(ctx, a.chain, &ins.Tx); err != nil {
194 return NewErrorResponse(err)
197 log.WithField("tx_id", ins.Tx.ID).Info("submit single tx")
198 return NewSuccessResponse(&submitTxResp{TxID: &ins.Tx.ID})
201 // EstimateTxGasResp estimate transaction consumed gas
202 type EstimateTxGasResp struct {
203 TotalNeu int64 `json:"total_neu"`
204 StorageNeu int64 `json:"storage_neu"`
205 VMNeu int64 `json:"vm_neu"`
208 // EstimateTxGas estimate consumed neu for transaction
209 func EstimateTxGas(template txbuilder.Template) (*EstimateTxGasResp, error) {
210 // base tx size and not include sign
211 data, err := template.Transaction.TxData.MarshalText()
215 baseTxSize := int64(len(data))
217 // extra tx size for sign witness parts
218 baseWitnessSize := int64(300)
219 lenSignInst := int64(len(template.SigningInstructions))
220 signSize := baseWitnessSize * lenSignInst
222 // total gas for tx storage
223 totalTxSizeGas, ok := checked.MulInt64(baseTxSize+signSize, consensus.StorageGasRate)
225 return nil, errors.New("calculate txsize gas got a math error")
228 // consume gas for run VM
229 totalP2WPKHGas := int64(0)
230 totalP2WSHGas := int64(0)
231 baseP2WPKHGas := int64(1419)
232 baseP2WSHGas := int64(2499)
234 for _, inpID := range template.Transaction.Tx.InputIDs {
235 sp, err := template.Transaction.Spend(inpID)
240 resOut, err := template.Transaction.Output(*sp.SpentOutputId)
245 if segwit.IsP2WPKHScript(resOut.ControlProgram.Code) {
246 totalP2WPKHGas += baseP2WPKHGas
247 } else if segwit.IsP2WSHScript(resOut.ControlProgram.Code) {
248 totalP2WSHGas += baseP2WSHGas
252 // total estimate gas
253 totalGas := totalTxSizeGas + totalP2WPKHGas + totalP2WSHGas
255 // rounding totalNeu with base rate 100000
256 totalNeu := float64(totalGas*consensus.VMGasRate) / defaultBaseRate
257 roundingNeu := math.Ceil(totalNeu)
258 estimateNeu := int64(roundingNeu) * int64(defaultBaseRate)
260 return &EstimateTxGasResp{
261 TotalNeu: estimateNeu,
262 StorageNeu: totalTxSizeGas * consensus.VMGasRate,
263 VMNeu: (totalP2WPKHGas + totalP2WSHGas) * consensus.VMGasRate,
267 // POST /estimate-transaction-gas
268 func (a *API) estimateTxGas(ctx context.Context, in struct {
269 TxTemplate txbuilder.Template `json:"transaction_template"`
271 txGasResp, err := EstimateTxGas(in.TxTemplate)
273 return NewErrorResponse(err)
275 return NewSuccessResponse(txGasResp)