10 log "github.com/sirupsen/logrus"
12 "github.com/bytom/blockchain/txbuilder"
13 "github.com/bytom/consensus"
14 "github.com/bytom/consensus/segwit"
15 "github.com/bytom/errors"
16 "github.com/bytom/math/checked"
17 "github.com/bytom/net/http/reqid"
18 "github.com/bytom/protocol/bc"
19 "github.com/bytom/protocol/bc/types"
22 var defaultTxTTL = 5 * time.Minute
24 func (a *API) actionDecoder(action string) (func([]byte) (txbuilder.Action, error), bool) {
25 var decoder func([]byte) (txbuilder.Action, error)
27 case "control_address":
28 decoder = txbuilder.DecodeControlAddressAction
29 case "control_program":
30 decoder = txbuilder.DecodeControlProgramAction
31 case "control_receiver":
32 decoder = txbuilder.DecodeControlReceiverAction
34 decoder = a.wallet.AssetReg.DecodeIssueAction
36 decoder = txbuilder.DecodeRetireAction
38 decoder = a.wallet.AccountMgr.DecodeSpendAction
39 case "spend_account_unspent_output":
40 decoder = a.wallet.AccountMgr.DecodeSpendUTXOAction
47 // TODO modify mergeActions to loadSpendAction
48 func mergeActions(req *BuildRequest) ([]map[string]interface{}, error) {
49 var actions []map[string]interface{}
50 actionMap := make(map[string]map[string]interface{})
52 for _, m := range req.Actions {
53 if actionType := m["type"].(string); actionType != "spend_account" {
54 actions = append(actions, m)
58 if m["amount"] == nil {
59 return nil, errEmptyAmount
62 amountNumber := m["amount"].(json.Number)
63 amount, err := amountNumber.Int64()
64 if err != nil || amount == 0 {
65 return nil, errBadAmount
68 actionKey := m["asset_id"].(string) + m["account_id"].(string)
69 if tmpM, ok := actionMap[actionKey]; ok {
70 if tmpM["amount"] == nil {
71 return nil, errEmptyAmount
74 tmpNumber := tmpM["amount"].(json.Number)
75 tmpAmount, err := tmpNumber.Int64()
76 if err != nil || tmpAmount == 0 {
77 return nil, errBadAmount
80 tmpM["amount"] = json.Number(fmt.Sprintf("%v", tmpAmount+amount))
82 actionMap[actionKey] = m
83 actions = append(actions, m)
90 func onlyHaveSpendActions(req *BuildRequest) bool {
92 for _, m := range req.Actions {
93 if actionType := m["type"].(string); strings.HasPrefix(actionType, "spend") {
98 return count == len(req.Actions)
101 func (a *API) buildSingle(ctx context.Context, req *BuildRequest) (*txbuilder.Template, error) {
102 err := a.filterAliases(ctx, req)
107 if onlyHaveSpendActions(req) {
108 return nil, errors.New("transaction only contain spend actions, didn't have output actions")
111 reqActions, err := mergeActions(req)
113 return nil, errors.WithDetail(err, "unmarshal json amount error in mergeActions")
116 actions := make([]txbuilder.Action, 0, len(reqActions))
117 for i, act := range reqActions {
118 typ, ok := act["type"].(string)
120 return nil, errors.WithDetailf(errBadActionType, "no action type provided on action %d", i)
122 decoder, ok := a.actionDecoder(typ)
124 return nil, errors.WithDetailf(errBadActionType, "unknown action type %q on action %d", typ, i)
127 // Remarshal to JSON, the action may have been modified when we
129 b, err := json.Marshal(act)
133 action, err := decoder(b)
135 return nil, errors.WithDetailf(errBadAction, "%s on action %d", err.Error(), i)
137 actions = append(actions, action)
140 ttl := req.TTL.Duration
144 maxTime := time.Now().Add(ttl)
146 tpl, err := txbuilder.Build(ctx, req.Tx, actions, maxTime, req.TimeRange)
147 if errors.Root(err) == txbuilder.ErrAction {
148 // append each of the inner errors contained in the data.
150 for _, innerErr := range errors.Data(err)["actions"].([]error) {
151 Errs = Errs + "<" + innerErr.Error() + ">"
153 err = errors.New(err.Error() + "-" + Errs)
159 // ensure null is never returned for signing instructions
160 if tpl.SigningInstructions == nil {
161 tpl.SigningInstructions = []*txbuilder.SigningInstruction{}
166 // POST /build-transaction
167 func (a *API) build(ctx context.Context, buildReqs *BuildRequest) Response {
168 subctx := reqid.NewSubContext(ctx, reqid.New())
170 tmpl, err := a.buildSingle(subctx, buildReqs)
172 return NewErrorResponse(err)
175 return NewSuccessResponse(tmpl)
178 type submitTxResp struct {
179 TxID *bc.Hash `json:"tx_id"`
182 // POST /submit-transaction
183 func (a *API) submit(ctx context.Context, ins struct {
184 Tx types.Tx `json:"raw_transaction"`
186 if err := txbuilder.FinalizeTx(ctx, a.chain, &ins.Tx); err != nil {
187 return NewErrorResponse(err)
190 log.WithField("tx_id", ins.Tx.ID).Info("submit single tx")
191 return NewSuccessResponse(&submitTxResp{TxID: &ins.Tx.ID})
194 // EstimateTxGasResp estimate transaction consumed gas
195 type EstimateTxGasResp struct {
196 TotalNeu int64 `json:"total_neu"`
197 StorageNeu int64 `json:"storage_neu"`
198 VMNeu int64 `json:"vm_neu"`
201 // POST /estimate-transaction-gas
202 func (a *API) estimateTxGas(ctx context.Context, in struct {
203 TxTemplate txbuilder.Template `json:"transaction_template"`
205 // base tx size and not include sign
206 data, err := in.TxTemplate.Transaction.TxData.MarshalText()
208 return NewErrorResponse(err)
210 baseTxSize := int64(len(data))
212 // extra tx size for sign witness parts
213 baseWitnessSize := int64(300)
214 lenSignInst := int64(len(in.TxTemplate.SigningInstructions))
215 signSize := baseWitnessSize * lenSignInst
217 // total gas for tx storage
218 totalTxSizeGas, ok := checked.MulInt64(baseTxSize+signSize, consensus.StorageGasRate)
220 return NewErrorResponse(errors.New("calculate txsize gas got a math error"))
223 // consume gas for run VM
224 totalP2WPKHGas := int64(0)
225 totalP2WSHGas := int64(0)
226 baseP2WPKHGas := int64(1419)
227 baseP2WSHGas := int64(2499)
229 for _, inpID := range in.TxTemplate.Transaction.Tx.InputIDs {
230 sp, err := in.TxTemplate.Transaction.Spend(inpID)
235 resOut, err := in.TxTemplate.Transaction.Output(*sp.SpentOutputId)
240 if segwit.IsP2WPKHScript(resOut.ControlProgram.Code) {
241 totalP2WPKHGas += baseP2WPKHGas
242 } else if segwit.IsP2WSHScript(resOut.ControlProgram.Code) {
243 totalP2WSHGas += baseP2WSHGas
247 // total estimate gas
248 totalGas := totalTxSizeGas + totalP2WPKHGas + totalP2WSHGas
250 txGasResp := &EstimateTxGasResp{
251 TotalNeu: totalGas * consensus.VMGasRate,
252 StorageNeu: totalTxSizeGas * consensus.VMGasRate,
253 VMNeu: (totalP2WPKHGas + totalP2WSHGas) * consensus.VMGasRate,
256 return NewSuccessResponse(txGasResp)