9 log "github.com/sirupsen/logrus"
11 "github.com/vapor/account"
12 "github.com/vapor/blockchain/txbuilder"
13 "github.com/vapor/errors"
14 "github.com/vapor/net/http/reqid"
15 "github.com/vapor/protocol/bc"
16 "github.com/vapor/protocol/bc/types"
20 defaultTxTTL = 30 * time.Minute
21 defaultBaseRate = float64(100000)
24 func (a *API) actionDecoder(action string) (func([]byte) (txbuilder.Action, error), bool) {
25 decoders := map[string]func([]byte) (txbuilder.Action, error){
26 "control_address": txbuilder.DecodeControlAddressAction,
27 "control_program": txbuilder.DecodeControlProgramAction,
28 "retire": txbuilder.DecodeRetireAction,
29 "cross_chain_out": txbuilder.DecodeCrossOutAction,
30 "vote_output": txbuilder.DecodeVoteOutputAction,
31 "spend_account": a.wallet.AccountMgr.DecodeSpendAction,
32 "spend_account_unspent_output": a.wallet.AccountMgr.DecodeSpendUTXOAction,
34 decoder, ok := decoders[action]
38 func onlyHaveInputActions(req *BuildRequest) (bool, error) {
40 for i, act := range req.Actions {
41 actionType, ok := act["type"].(string)
43 return false, errors.WithDetailf(ErrBadActionType, "no action type provided on action %d", i)
46 if strings.HasPrefix(actionType, "spend") || actionType == "issue" {
51 return count == len(req.Actions), nil
54 func (a *API) buildSingle(ctx context.Context, req *BuildRequest) (*txbuilder.Template, error) {
55 if err := a.checkRequestValidity(ctx, req); err != nil {
58 actions, err := a.mergeSpendActions(req)
63 maxTime := time.Now().Add(req.TTL.Duration)
64 tpl, err := txbuilder.Build(ctx, req.Tx, actions, maxTime, req.TimeRange)
65 if errors.Root(err) == txbuilder.ErrAction {
66 // append each of the inner errors contained in the data.
69 for i, innerErr := range errors.Data(err)["actions"].([]error) {
71 rootErr = errors.Root(innerErr)
73 Errs = Errs + innerErr.Error()
75 err = errors.WithDetail(rootErr, Errs)
81 // ensure null is never returned for signing instructions
82 if tpl.SigningInstructions == nil {
83 tpl.SigningInstructions = []*txbuilder.SigningInstruction{}
88 // POST /build-transaction
89 func (a *API) build(ctx context.Context, buildReqs *BuildRequest) Response {
90 subctx := reqid.NewSubContext(ctx, reqid.New())
91 tmpl, err := a.buildSingle(subctx, buildReqs)
93 return NewErrorResponse(err)
96 return NewSuccessResponse(tmpl)
98 func (a *API) checkRequestValidity(ctx context.Context, req *BuildRequest) error {
99 if err := a.completeMissingIDs(ctx, req); err != nil {
103 if req.TTL.Duration == 0 {
104 req.TTL.Duration = defaultTxTTL
107 if ok, err := onlyHaveInputActions(req); err != nil {
110 return errors.WithDetail(ErrBadActionConstruction, "transaction contains only input actions and no output actions")
115 func (a *API) mergeSpendActions(req *BuildRequest) ([]txbuilder.Action, error) {
116 actions := make([]txbuilder.Action, 0, len(req.Actions))
117 for i, act := range req.Actions {
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)
139 actions = account.MergeSpendAction(actions)
143 func (a *API) buildTxs(ctx context.Context, req *BuildRequest) ([]*txbuilder.Template, error) {
144 if err := a.checkRequestValidity(ctx, req); err != nil {
147 actions, err := a.mergeSpendActions(req)
152 builder := txbuilder.NewBuilder(time.Now().Add(req.TTL.Duration))
153 tpls := []*txbuilder.Template{}
154 for _, action := range actions {
155 if action.ActionType() == "spend_account" {
156 tpls, err = account.SpendAccountChain(ctx, builder, action)
158 err = action.Build(ctx, builder)
167 tpl, _, err := builder.Build()
173 tpls = append(tpls, tpl)
177 // POST /build-chain-transactions
178 func (a *API) buildChainTxs(ctx context.Context, buildReqs *BuildRequest) Response {
179 subctx := reqid.NewSubContext(ctx, reqid.New())
180 tmpls, err := a.buildTxs(subctx, buildReqs)
182 return NewErrorResponse(err)
184 return NewSuccessResponse(tmpls)
187 type submitTxResp struct {
188 TxID *bc.Hash `json:"tx_id"`
191 // POST /submit-transaction
192 func (a *API) submit(ctx context.Context, ins struct {
193 Tx types.Tx `json:"raw_transaction"`
195 if err := txbuilder.FinalizeTx(ctx, a.chain, &ins.Tx); err != nil {
196 return NewErrorResponse(err)
199 log.WithField("tx_id", ins.Tx.ID.String()).Info("submit single tx")
200 return NewSuccessResponse(&submitTxResp{TxID: &ins.Tx.ID})
203 type submitTxsResp struct {
204 TxID []*bc.Hash `json:"tx_id"`
207 // POST /submit-transactions
208 func (a *API) submitTxs(ctx context.Context, ins struct {
209 Tx []types.Tx `json:"raw_transactions"`
211 txHashs := []*bc.Hash{}
212 for i := range ins.Tx {
213 if err := txbuilder.FinalizeTx(ctx, a.chain, &ins.Tx[i]); err != nil {
214 return NewErrorResponse(err)
216 log.WithField("tx_id", ins.Tx[i].ID.String()).Info("submit single tx")
217 txHashs = append(txHashs, &ins.Tx[i].ID)
219 return NewSuccessResponse(&submitTxsResp{TxID: txHashs})
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 := txbuilder.EstimateTxGas(in.TxTemplate)
228 return NewErrorResponse(err)
230 return NewSuccessResponse(txGasResp)