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