10 log "github.com/sirupsen/logrus"
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"
24 defaultTxTTL = 5 * time.Minute
25 defaultBaseRate = float64(100000)
28 func (a *API) actionDecoder(action string) (func([]byte) (txbuilder.Action, error), bool) {
29 var decoder func([]byte) (txbuilder.Action, error)
31 case "control_address":
32 decoder = txbuilder.DecodeControlAddressAction
33 case "control_program":
34 decoder = txbuilder.DecodeControlProgramAction
35 case "control_receiver":
36 decoder = txbuilder.DecodeControlReceiverAction
38 decoder = a.wallet.AssetReg.DecodeIssueAction
40 decoder = txbuilder.DecodeRetireAction
42 decoder = a.wallet.AccountMgr.DecodeSpendAction
43 case "spend_account_unspent_output":
44 decoder = a.wallet.AccountMgr.DecodeSpendUTXOAction
51 func onlyHaveSpendActions(req *BuildRequest) bool {
53 for _, m := range req.Actions {
54 if actionType := m["type"].(string); strings.HasPrefix(actionType, "spend") {
59 return count == len(req.Actions)
62 func (a *API) buildSingle(ctx context.Context, req *BuildRequest) (*txbuilder.Template, error) {
63 if err := a.completeMissingIds(ctx, req); err != nil {
67 if onlyHaveSpendActions(req) {
68 return nil, errors.New("transaction only contain spend actions, didn't have output actions")
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)
76 return nil, errors.WithDetailf(errBadActionType, "no action type provided on action %d", i)
78 decoder, ok := a.actionDecoder(typ)
80 return nil, errors.WithDetailf(errBadActionType, "unknown action type %q on action %d", typ, i)
83 // Remarshal to JSON, the action may have been modified when we
85 b, err := json.Marshal(act)
89 action, err := decoder(b)
91 return nil, errors.WithDetailf(errBadAction, "%s on action %d", err.Error(), i)
94 if typ == "spend_account" {
95 spendActions = append(spendActions, action)
97 actions = append(actions, action)
100 actions = append(account.MergeSpendAction(spendActions), actions...)
102 ttl := req.TTL.Duration
106 maxTime := time.Now().Add(ttl)
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.
112 for _, innerErr := range errors.Data(err)["actions"].([]error) {
113 Errs = Errs + "<" + innerErr.Error() + ">"
115 err = errors.New(err.Error() + "-" + Errs)
121 // ensure null is never returned for signing instructions
122 if tpl.SigningInstructions == nil {
123 tpl.SigningInstructions = []*txbuilder.SigningInstruction{}
128 // POST /build-transaction
129 func (a *API) build(ctx context.Context, buildReqs *BuildRequest) Response {
130 subctx := reqid.NewSubContext(ctx, reqid.New())
132 tmpl, err := a.buildSingle(subctx, buildReqs)
134 return NewErrorResponse(err)
137 return NewSuccessResponse(tmpl)
140 type submitTxResp struct {
141 TxID *bc.Hash `json:"tx_id"`
144 // POST /submit-transaction
145 func (a *API) submit(ctx context.Context, ins struct {
146 Tx types.Tx `json:"raw_transaction"`
148 if err := txbuilder.FinalizeTx(ctx, a.chain, &ins.Tx); err != nil {
149 return NewErrorResponse(err)
152 log.WithField("tx_id", ins.Tx.ID.String()).Info("submit single tx")
153 return NewSuccessResponse(&submitTxResp{TxID: &ins.Tx.ID})
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"`
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()
170 baseTxSize := int64(len(data))
172 // extra tx size for sign witness parts
173 baseWitnessSize := int64(300)
174 lenSignInst := int64(len(template.SigningInstructions))
175 signSize := baseWitnessSize * lenSignInst
177 // total gas for tx storage
178 totalTxSizeGas, ok := checked.MulInt64(baseTxSize+signSize, consensus.StorageGasRate)
180 return nil, errors.New("calculate txsize gas got a math error")
183 // consume gas for run VM
184 totalP2WPKHGas := int64(0)
185 totalP2WSHGas := int64(0)
186 baseP2WPKHGas := int64(1419)
187 baseP2WSHGas := int64(2499)
189 for _, inpID := range template.Transaction.Tx.InputIDs {
190 sp, err := template.Transaction.Spend(inpID)
195 resOut, err := template.Transaction.Output(*sp.SpentOutputId)
200 if segwit.IsP2WPKHScript(resOut.ControlProgram.Code) {
201 totalP2WPKHGas += baseP2WPKHGas
202 } else if segwit.IsP2WSHScript(resOut.ControlProgram.Code) {
203 totalP2WSHGas += baseP2WSHGas
207 // total estimate gas
208 totalGas := totalTxSizeGas + totalP2WPKHGas + totalP2WSHGas
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)
215 return &EstimateTxGasResp{
216 TotalNeu: estimateNeu,
217 StorageNeu: totalTxSizeGas * consensus.VMGasRate,
218 VMNeu: (totalP2WPKHGas + totalP2WSHGas) * consensus.VMGasRate,
222 // POST /estimate-transaction-gas
223 func (a *API) estimateTxGas(ctx context.Context, in struct {
224 TxTemplate txbuilder.Template `json:"transaction_template"`
226 txGasResp, err := EstimateTxGas(in.TxTemplate)
228 return NewErrorResponse(err)
230 return NewSuccessResponse(txGasResp)