github.com/bytom/bytom@v1.1.2-0.20221014091027-bbcba3df6075/api/transact.go (about) 1 package api 2 3 import ( 4 "context" 5 "encoding/json" 6 "strings" 7 "time" 8 9 log "github.com/sirupsen/logrus" 10 11 "github.com/bytom/bytom/account" 12 "github.com/bytom/bytom/blockchain/txbuilder" 13 "github.com/bytom/bytom/errors" 14 "github.com/bytom/bytom/net/http/reqid" 15 "github.com/bytom/bytom/protocol/bc" 16 "github.com/bytom/bytom/protocol/bc/types" 17 ) 18 19 var ( 20 defaultTxTTL = 30 * time.Minute 21 defaultBaseRate = float64(100000) 22 ) 23 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 "issue": a.wallet.AssetReg.DecodeIssueAction, 29 "retire": txbuilder.DecodeRetireAction, 30 "vote_output": txbuilder.DecodeVoteOutputAction, 31 "register_contract": txbuilder.DecodeRegisterAction, 32 "spend_account": a.wallet.AccountMgr.DecodeSpendAction, 33 "spend_account_unspent_output": a.wallet.AccountMgr.DecodeSpendUTXOAction, 34 "veto": a.wallet.AccountMgr.DecodeVetoAction, 35 } 36 decoder, ok := decoders[action] 37 return decoder, ok 38 } 39 40 func onlyHaveInputActions(req *BuildRequest) (bool, error) { 41 count := 0 42 for i, act := range req.Actions { 43 actionType, ok := act["type"].(string) 44 if !ok { 45 return false, errors.WithDetailf(ErrBadActionType, "no action type provided on action %d", i) 46 } 47 48 if strings.HasPrefix(actionType, "spend") || actionType == "issue" || actionType == "veto" { 49 count++ 50 } 51 } 52 53 return count == len(req.Actions), nil 54 } 55 56 func (a *API) buildSingle(ctx context.Context, req *BuildRequest) (*txbuilder.Template, error) { 57 if err := a.checkRequestValidity(ctx, req); err != nil { 58 return nil, err 59 } 60 actions, err := a.mergeSpendActions(req) 61 if err != nil { 62 return nil, err 63 } 64 65 maxTime := time.Now().Add(req.TTL.Duration) 66 tpl, err := txbuilder.Build(ctx, req.Tx, actions, maxTime, req.TimeRange) 67 if errors.Root(err) == txbuilder.ErrAction { 68 // append each of the inner errors contained in the data. 69 var Errs string 70 var rootErr error 71 for i, innerErr := range errors.Data(err)["actions"].([]error) { 72 if i == 0 { 73 rootErr = errors.Root(innerErr) 74 } 75 Errs = Errs + innerErr.Error() 76 } 77 err = errors.WithDetail(rootErr, Errs) 78 } 79 if err != nil { 80 return nil, err 81 } 82 83 // ensure null is never returned for signing instructions 84 if tpl.SigningInstructions == nil { 85 tpl.SigningInstructions = []*txbuilder.SigningInstruction{} 86 } 87 return tpl, nil 88 } 89 90 // POST /build-transaction 91 func (a *API) build(ctx context.Context, buildReqs *BuildRequest) Response { 92 subctx := reqid.NewSubContext(ctx, reqid.New()) 93 tmpl, err := a.buildSingle(subctx, buildReqs) 94 if err != nil { 95 return NewErrorResponse(err) 96 } 97 98 return NewSuccessResponse(tmpl) 99 } 100 func (a *API) checkRequestValidity(ctx context.Context, req *BuildRequest) error { 101 if err := a.completeMissingIDs(ctx, req); err != nil { 102 return err 103 } 104 105 if req.TTL.Duration == 0 { 106 req.TTL.Duration = defaultTxTTL 107 } 108 109 if ok, err := onlyHaveInputActions(req); err != nil { 110 return err 111 } else if ok { 112 return errors.WithDetail(ErrBadActionConstruction, "transaction contains only input actions and no output actions") 113 } 114 return nil 115 } 116 117 func (a *API) mergeSpendActions(req *BuildRequest) ([]txbuilder.Action, error) { 118 actions := make([]txbuilder.Action, 0, len(req.Actions)) 119 for i, act := range req.Actions { 120 typ, ok := act["type"].(string) 121 if !ok { 122 return nil, errors.WithDetailf(ErrBadActionType, "no action type provided on action %d", i) 123 } 124 decoder, ok := a.actionDecoder(typ) 125 if !ok { 126 return nil, errors.WithDetailf(ErrBadActionType, "unknown action type %q on action %d", typ, i) 127 } 128 129 // Remarshal to JSON, the action may have been modified when we 130 // filtered aliases. 131 b, err := json.Marshal(act) 132 if err != nil { 133 return nil, err 134 } 135 action, err := decoder(b) 136 if err != nil { 137 return nil, errors.WithDetailf(ErrBadAction, "%s on action %d", err.Error(), i) 138 } 139 actions = append(actions, action) 140 } 141 actions = account.MergeSpendAction(actions) 142 return actions, nil 143 } 144 145 func (a *API) buildTxs(ctx context.Context, req *BuildRequest) ([]*txbuilder.Template, error) { 146 if err := a.checkRequestValidity(ctx, req); err != nil { 147 return nil, err 148 } 149 actions, err := a.mergeSpendActions(req) 150 if err != nil { 151 return nil, err 152 } 153 154 builder := txbuilder.NewBuilder(time.Now().Add(req.TTL.Duration)) 155 tpls := []*txbuilder.Template{} 156 for _, action := range actions { 157 if action.ActionType() == "spend_account" { 158 tpls, err = account.SpendAccountChain(ctx, builder, action) 159 } else { 160 err = action.Build(ctx, builder) 161 } 162 163 if err != nil { 164 builder.Rollback() 165 return nil, err 166 } 167 } 168 169 tpl, _, err := builder.Build() 170 if err != nil { 171 builder.Rollback() 172 return nil, err 173 } 174 175 tpls = append(tpls, tpl) 176 return tpls, nil 177 } 178 179 // POST /build-chain-transactions 180 func (a *API) buildChainTxs(ctx context.Context, buildReqs *BuildRequest) Response { 181 subctx := reqid.NewSubContext(ctx, reqid.New()) 182 tmpls, err := a.buildTxs(subctx, buildReqs) 183 if err != nil { 184 return NewErrorResponse(err) 185 } 186 return NewSuccessResponse(tmpls) 187 } 188 189 type submitTxResp struct { 190 TxID *bc.Hash `json:"tx_id"` 191 } 192 193 // POST /submit-transaction 194 func (a *API) submit(ctx context.Context, ins struct { 195 Tx types.Tx `json:"raw_transaction"` 196 }) Response { 197 if err := txbuilder.FinalizeTx(ctx, a.chain, &ins.Tx); err != nil { 198 return NewErrorResponse(err) 199 } 200 201 log.WithField("tx_id", ins.Tx.ID.String()).Info("submit single tx") 202 return NewSuccessResponse(&submitTxResp{TxID: &ins.Tx.ID}) 203 } 204 205 type submitTxsResp struct { 206 TxID []*bc.Hash `json:"tx_id"` 207 } 208 209 // POST /submit-transactions 210 func (a *API) submitTxs(ctx context.Context, ins struct { 211 Tx []types.Tx `json:"raw_transactions"` 212 }) Response { 213 txHashs := []*bc.Hash{} 214 for i := range ins.Tx { 215 if err := txbuilder.FinalizeTx(ctx, a.chain, &ins.Tx[i]); err != nil { 216 return NewErrorResponse(err) 217 } 218 log.WithField("tx_id", ins.Tx[i].ID.String()).Info("submit single tx") 219 txHashs = append(txHashs, &ins.Tx[i].ID) 220 } 221 return NewSuccessResponse(&submitTxsResp{TxID: txHashs}) 222 } 223 224 // POST /estimate-transaction-gas 225 func (a *API) estimateTxGas(ctx context.Context, in struct { 226 TxTemplate txbuilder.Template `json:"transaction_template"` 227 }) Response { 228 txGasResp, err := txbuilder.EstimateTxGas(in.TxTemplate) 229 if err != nil { 230 return NewErrorResponse(err) 231 } 232 233 return NewSuccessResponse(txGasResp) 234 } 235 236 // POST /estimate-chain-transaction-gas 237 func (a *API) estimateChainTxGas(ctx context.Context, in struct { 238 TxTemplates []txbuilder.Template `json:"transaction_templates"` 239 }) Response { 240 txGasResp, err := txbuilder.EstimateChainTxGas(in.TxTemplates) 241 if err != nil { 242 return NewErrorResponse(err) 243 } 244 245 return NewSuccessResponse(txGasResp) 246 }