10 log "github.com/sirupsen/logrus"
12 "github.com/bytom/blockchain/pseudohsm"
13 "github.com/bytom/blockchain/txbuilder"
14 "github.com/bytom/consensus"
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 func mergeActions(req *BuildRequest) []map[string]interface{} {
48 var actions []map[string]interface{}
49 actionMap := make(map[string]map[string]interface{})
51 for _, m := range req.Actions {
52 if actionType := m["type"].(string); actionType != "spend_account" {
53 actions = append(actions, m)
57 actionKey := m["asset_id"].(string) + m["account_id"].(string)
58 amountNumber := m["amount"].(json.Number)
59 amount, _ := amountNumber.Int64()
61 if tmpM, ok := actionMap[actionKey]; ok {
62 tmpNumber, _ := tmpM["amount"].(json.Number)
63 tmpAmount, _ := tmpNumber.Int64()
64 tmpM["amount"] = json.Number(fmt.Sprintf("%v", tmpAmount+amount))
66 actionMap[actionKey] = m
67 actions = append(actions, m)
74 func onlyHaveSpendActions(req *BuildRequest) bool {
76 for _, m := range req.Actions {
77 if actionType := m["type"].(string); strings.HasPrefix(actionType, "spend") {
82 return count == len(req.Actions)
85 func (a *API) buildSingle(ctx context.Context, req *BuildRequest) (*txbuilder.Template, error) {
86 err := a.filterAliases(ctx, req)
91 if onlyHaveSpendActions(req) {
92 return nil, errors.New("transaction only contain spend actions, didn't have output actions")
95 reqActions := mergeActions(req)
96 actions := make([]txbuilder.Action, 0, len(reqActions))
97 for i, act := range reqActions {
98 typ, ok := act["type"].(string)
100 return nil, errors.WithDetailf(errBadActionType, "no action type provided on action %d", i)
102 decoder, ok := a.actionDecoder(typ)
104 return nil, errors.WithDetailf(errBadActionType, "unknown action type %q on action %d", typ, i)
107 // Remarshal to JSON, the action may have been modified when we
109 b, err := json.Marshal(act)
113 action, err := decoder(b)
115 return nil, errors.WithDetailf(errBadAction, "%s on action %d", err.Error(), i)
117 actions = append(actions, action)
120 ttl := req.TTL.Duration
124 maxTime := time.Now().Add(ttl)
126 tpl, err := txbuilder.Build(ctx, req.Tx, actions, maxTime, req.TimeRange)
127 if errors.Root(err) == txbuilder.ErrAction {
128 // append each of the inner errors contained in the data.
130 for _, innerErr := range errors.Data(err)["actions"].([]error) {
131 Errs = Errs + "<" + innerErr.Error() + ">"
133 err = errors.New(err.Error() + "-" + Errs)
139 // ensure null is never returned for signing instructions
140 if tpl.SigningInstructions == nil {
141 tpl.SigningInstructions = []*txbuilder.SigningInstruction{}
146 // POST /build-transaction
147 func (a *API) build(ctx context.Context, buildReqs *BuildRequest) Response {
148 subctx := reqid.NewSubContext(ctx, reqid.New())
150 tmpl, err := a.buildSingle(subctx, buildReqs)
152 return NewErrorResponse(err)
155 return NewSuccessResponse(tmpl)
158 func (a *API) submitSingle(ctx context.Context, tpl *txbuilder.Template) (map[string]string, error) {
159 if tpl.Transaction == nil {
160 return nil, errors.Wrap(txbuilder.ErrMissingRawTx)
163 if err := txbuilder.FinalizeTx(ctx, a.chain, tpl.Transaction); err != nil {
164 return nil, errors.Wrapf(err, "tx %s", tpl.Transaction.ID.String())
167 return map[string]string{"tx_id": tpl.Transaction.ID.String()}, nil
170 type submitTxResp struct {
171 TxID *bc.Hash `json:"tx_id"`
174 // POST /submit-transaction
175 func (a *API) submit(ctx context.Context, ins struct {
176 Tx types.Tx `json:"raw_transaction"`
178 if err := txbuilder.FinalizeTx(ctx, a.chain, &ins.Tx); err != nil {
179 return NewErrorResponse(err)
182 log.WithField("tx_id", ins.Tx.ID).Info("submit single tx")
183 return NewSuccessResponse(&submitTxResp{TxID: &ins.Tx.ID})
186 // POST /sign-submit-transaction
187 func (a *API) signSubmit(ctx context.Context, x struct {
188 Password string `json:"password"`
189 Txs txbuilder.Template `json:"transaction"`
191 if err := txbuilder.Sign(ctx, &x.Txs, nil, x.Password, a.pseudohsmSignTemplate); err != nil {
192 log.WithField("build err", err).Error("fail on sign transaction.")
193 return NewErrorResponse(err)
196 if signCount, complete := txbuilder.SignInfo(&x.Txs); !complete && signCount == 0 {
197 return NewErrorResponse(pseudohsm.ErrLoadKey)
199 log.Info("Sign Transaction complete.")
201 txID, err := a.submitSingle(nil, &x.Txs)
203 log.WithField("err", err).Error("submit single tx")
204 return NewErrorResponse(err)
207 log.WithField("tx_id", txID["tx_id"]).Info("submit single tx")
208 return NewSuccessResponse(txID)
211 type EstimateTxGasResp struct {
212 LeftBTM int64 `json:"left_btm"`
213 ConsumedBTM int64 `json:"consumed_btm"`
214 LeftGas int64 `json:"left_gas"`
215 ConsumedGas int64 `json:"consumed_gas"`
216 StorageGas int64 `json:"storage_gas"`
217 VMGas int64 `json:"vm_gas"`
220 // POST /estimate-transaction-gas
221 func (a *API) estimateTxGas(ctx context.Context, ins struct {
222 Tx types.Tx `json:"raw_transaction"`
224 gasState, err := txbuilder.EstimateTxGas(a.chain, &ins.Tx)
226 return NewErrorResponse(err)
229 btmLeft, ok := checked.MulInt64(gasState.GasLeft, consensus.VMGasRate)
231 return NewErrorResponse(errors.New("calculate btmleft got a math error"))
234 txGasResp := &EstimateTxGasResp{
236 ConsumedBTM: int64(gasState.BTMValue) - btmLeft,
237 LeftGas: gasState.GasLeft,
238 ConsumedGas: gasState.GasUsed,
239 StorageGas: gasState.StorageGas,
240 VMGas: gasState.GasUsed - gasState.StorageGas,
243 return NewSuccessResponse(txGasResp)