11 log "github.com/sirupsen/logrus"
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"
23 var defaultTxTTL = 5 * time.Minute
25 func (a *API) actionDecoder(action string) (func([]byte) (txbuilder.Action, error), bool) {
26 var decoder func([]byte) (txbuilder.Action, error)
28 case "control_address":
29 decoder = txbuilder.DecodeControlAddressAction
30 case "control_program":
31 decoder = txbuilder.DecodeControlProgramAction
32 case "control_receiver":
33 decoder = txbuilder.DecodeControlReceiverAction
35 decoder = a.wallet.AssetReg.DecodeIssueAction
37 decoder = txbuilder.DecodeRetireAction
39 decoder = a.wallet.AccountMgr.DecodeSpendAction
40 case "spend_account_unspent_output":
41 decoder = a.wallet.AccountMgr.DecodeSpendUTXOAction
48 // TODO modify mergeActions to loadSpendAction
49 func mergeActions(req *BuildRequest) ([]map[string]interface{}, error) {
50 var actions []map[string]interface{}
51 actionMap := make(map[string]map[string]interface{})
53 for _, m := range req.Actions {
54 if actionType := m["type"].(string); actionType != "spend_account" {
55 actions = append(actions, m)
59 if m["amount"] == nil {
60 return nil, errEmptyAmount
63 amountNumber := m["amount"].(json.Number)
64 amount, err := amountNumber.Int64()
65 if err != nil || amount == 0 {
66 return nil, errBadAmount
69 actionKey := m["asset_id"].(string) + m["account_id"].(string)
70 if tmpM, ok := actionMap[actionKey]; ok {
71 if tmpM["amount"] == nil {
72 return nil, errEmptyAmount
75 tmpNumber := tmpM["amount"].(json.Number)
76 tmpAmount, err := tmpNumber.Int64()
77 if err != nil || tmpAmount == 0 {
78 return nil, errBadAmount
81 tmpM["amount"] = json.Number(fmt.Sprintf("%v", tmpAmount+amount))
83 actionMap[actionKey] = m
84 actions = append(actions, m)
91 func onlyHaveSpendActions(req *BuildRequest) bool {
93 for _, m := range req.Actions {
94 if actionType := m["type"].(string); strings.HasPrefix(actionType, "spend") {
99 return count == len(req.Actions)
102 func (a *API) buildSingle(ctx context.Context, req *BuildRequest) (*txbuilder.Template, error) {
103 err := a.filterAliases(ctx, req)
108 if onlyHaveSpendActions(req) {
109 return nil, errors.New("transaction only contain spend actions, didn't have output actions")
112 reqActions, err := mergeActions(req)
114 return nil, errors.WithDetail(err, "unmarshal json amount error in mergeActions")
117 actions := make([]txbuilder.Action, 0, len(reqActions))
118 for i, act := range reqActions {
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)
138 actions = append(actions, action)
141 ttl := req.TTL.Duration
145 maxTime := time.Now().Add(ttl)
147 tpl, err := txbuilder.Build(ctx, req.Tx, actions, maxTime, req.TimeRange)
148 if errors.Root(err) == txbuilder.ErrAction {
149 // append each of the inner errors contained in the data.
151 for _, innerErr := range errors.Data(err)["actions"].([]error) {
152 Errs = Errs + "<" + innerErr.Error() + ">"
154 err = errors.New(err.Error() + "-" + Errs)
160 // ensure null is never returned for signing instructions
161 if tpl.SigningInstructions == nil {
162 tpl.SigningInstructions = []*txbuilder.SigningInstruction{}
167 // POST /build-transaction
168 func (a *API) build(ctx context.Context, buildReqs *BuildRequest) Response {
169 subctx := reqid.NewSubContext(ctx, reqid.New())
171 tmpl, err := a.buildSingle(subctx, buildReqs)
173 return NewErrorResponse(err)
176 return NewSuccessResponse(tmpl)
179 type submitTxResp struct {
180 TxID *bc.Hash `json:"tx_id"`
183 // POST /submit-transaction
184 func (a *API) submit(ctx context.Context, ins struct {
185 Tx types.Tx `json:"raw_transaction"`
187 if err := txbuilder.FinalizeTx(ctx, a.chain, &ins.Tx); err != nil {
188 return NewErrorResponse(err)
191 log.WithField("tx_id", ins.Tx.ID).Info("submit single tx")
192 return NewSuccessResponse(&submitTxResp{TxID: &ins.Tx.ID})
195 // EstimateTxGasResp estimate transaction consumed gas
196 type EstimateTxGasResp struct {
197 TotalNeu int64 `json:"total_neu"`
198 StorageNeu int64 `json:"storage_neu"`
199 VMNeu int64 `json:"vm_neu"`
202 // POST /estimate-transaction-gas
203 func (a *API) estimateTxGas(ctx context.Context, in struct {
204 TxTemplate txbuilder.Template `json:"transaction_template"`
206 // base tx size and not include sign
207 data, err := in.TxTemplate.Transaction.TxData.MarshalText()
209 return NewErrorResponse(err)
211 baseTxSize := int64(len(data))
213 // extra tx size for sign witness parts
214 baseWitnessSize := int64(300)
215 lenSignInst := int64(len(in.TxTemplate.SigningInstructions))
216 signSize := baseWitnessSize * lenSignInst
218 // total gas for tx storage
219 totalTxSizeGas, ok := checked.MulInt64(baseTxSize+signSize, consensus.StorageGasRate)
221 return NewErrorResponse(errors.New("calculate txsize gas got a math error"))
224 // consume gas for run VM
225 totalP2WPKHGas := int64(0)
226 totalP2WSHGas := int64(0)
227 baseP2WPKHGas := int64(1419)
228 baseP2WSHGas := int64(2499)
230 for _, inpID := range in.TxTemplate.Transaction.Tx.InputIDs {
231 sp, err := in.TxTemplate.Transaction.Spend(inpID)
236 resOut, err := in.TxTemplate.Transaction.Output(*sp.SpentOutputId)
241 if segwit.IsP2WPKHScript(resOut.ControlProgram.Code) {
242 totalP2WPKHGas += baseP2WPKHGas
243 } else if segwit.IsP2WSHScript(resOut.ControlProgram.Code) {
244 totalP2WSHGas += baseP2WSHGas
248 // total estimate gas
249 totalGas := totalTxSizeGas + totalP2WPKHGas + totalP2WSHGas
251 // rounding totalNeu with base 100000
252 totalNeu := float64(totalGas*consensus.VMGasRate) / float64(100000)
253 roundingNeu := math.Ceil(totalNeu)
254 estimateNeu := int64(roundingNeu) * int64(100000)
256 txGasResp := &EstimateTxGasResp{
257 TotalNeu: estimateNeu,
258 StorageNeu: totalTxSizeGas * consensus.VMGasRate,
259 VMNeu: (totalP2WPKHGas + totalP2WSHGas) * consensus.VMGasRate,
262 return NewSuccessResponse(txGasResp)