10 log "github.com/sirupsen/logrus"
12 "github.com/bytom/account"
13 "github.com/bytom/blockchain/txbuilder"
14 "github.com/bytom/consensus"
15 "github.com/bytom/consensus/segwit"
16 "github.com/bytom/errors"
17 "github.com/bytom/math/checked"
18 "github.com/bytom/net/http/reqid"
19 "github.com/bytom/protocol/bc"
20 "github.com/bytom/protocol/bc/types"
24 defaultTxTTL = 5 * time.Minute
25 defaultBaseRate = float64(100000)
28 func (a *API) actionDecoder(action string) (func([]byte) (txbuilder.Action, error), bool) {
29 decoders := map[string]func([]byte) (txbuilder.Action, error){
30 "control_address": txbuilder.DecodeControlAddressAction,
31 "control_program": txbuilder.DecodeControlProgramAction,
32 "issue": a.wallet.AssetReg.DecodeIssueAction,
33 "retire": txbuilder.DecodeRetireAction,
34 "spend_account": a.wallet.AccountMgr.DecodeSpendAction,
35 "spend_account_unspent_output": a.wallet.AccountMgr.DecodeSpendUTXOAction,
37 decoder, ok := decoders[action]
41 func onlyHaveInputActions(req *BuildRequest) (bool, error) {
43 for i, act := range req.Actions {
44 actionType, ok := act["type"].(string)
46 return false, errors.WithDetailf(ErrBadActionType, "no action type provided on action %d", i)
49 if strings.HasPrefix(actionType, "spend") || actionType == "issue" {
54 return count == len(req.Actions), nil
57 func (a *API) buildSingle(ctx context.Context, req *BuildRequest) (*txbuilder.Template, error) {
58 if err := a.completeMissingIDs(ctx, req); err != nil {
62 if ok, err := onlyHaveInputActions(req); err != nil {
65 return nil, errors.WithDetail(ErrBadActionConstruction, "transaction contains only input actions and no output actions")
68 actions := make([]txbuilder.Action, 0, len(req.Actions))
69 for i, act := range req.Actions {
70 typ, ok := act["type"].(string)
72 return nil, errors.WithDetailf(ErrBadActionType, "no action type provided on action %d", i)
74 decoder, ok := a.actionDecoder(typ)
76 return nil, errors.WithDetailf(ErrBadActionType, "unknown action type %q on action %d", typ, i)
79 // Remarshal to JSON, the action may have been modified when we
81 b, err := json.Marshal(act)
85 action, err := decoder(b)
87 return nil, errors.WithDetailf(ErrBadAction, "%s on action %d", err.Error(), i)
89 actions = append(actions, action)
91 actions = account.MergeSpendAction(actions)
93 ttl := req.TTL.Duration
97 maxTime := time.Now().Add(ttl)
99 tpl, err := txbuilder.Build(ctx, req.Tx, actions, maxTime, req.TimeRange)
100 if errors.Root(err) == txbuilder.ErrAction {
101 // append each of the inner errors contained in the data.
104 for i, innerErr := range errors.Data(err)["actions"].([]error) {
106 rootErr = errors.Root(innerErr)
108 Errs = Errs + innerErr.Error()
110 err = errors.WithDetail(rootErr, Errs)
116 // ensure null is never returned for signing instructions
117 if tpl.SigningInstructions == nil {
118 tpl.SigningInstructions = []*txbuilder.SigningInstruction{}
123 // POST /build-transaction
124 func (a *API) build(ctx context.Context, buildReqs *BuildRequest) Response {
125 subctx := reqid.NewSubContext(ctx, reqid.New())
127 tmpl, err := a.buildSingle(subctx, buildReqs)
129 return NewErrorResponse(err)
132 return NewSuccessResponse(tmpl)
135 type submitTxResp struct {
136 TxID *bc.Hash `json:"tx_id"`
139 // POST /submit-transaction
140 func (a *API) submit(ctx context.Context, ins struct {
141 Tx types.Tx `json:"raw_transaction"`
143 if err := txbuilder.FinalizeTx(ctx, a.chain, &ins.Tx); err != nil {
144 return NewErrorResponse(err)
147 log.WithField("tx_id", ins.Tx.ID.String()).Info("submit single tx")
148 return NewSuccessResponse(&submitTxResp{TxID: &ins.Tx.ID})
151 // EstimateTxGasResp estimate transaction consumed gas
152 type EstimateTxGasResp struct {
153 TotalNeu int64 `json:"total_neu"`
154 StorageNeu int64 `json:"storage_neu"`
155 VMNeu int64 `json:"vm_neu"`
158 // EstimateTxGas estimate consumed neu for transaction
159 func EstimateTxGas(template txbuilder.Template) (*EstimateTxGasResp, error) {
160 // base tx size and not include sign
161 data, err := template.Transaction.TxData.MarshalText()
165 baseTxSize := int64(len(data))
167 // extra tx size for sign witness parts
168 signSize := estimateSignSize(template.SigningInstructions)
170 // total gas for tx storage
171 totalTxSizeGas, ok := checked.MulInt64(baseTxSize+signSize, consensus.StorageGasRate)
173 return nil, errors.New("calculate txsize gas got a math error")
176 // consume gas for run VM
177 totalP2WPKHGas := int64(0)
178 totalP2WSHGas := int64(0)
179 baseP2WPKHGas := int64(1419)
181 for pos, inpID := range template.Transaction.Tx.InputIDs {
182 sp, err := template.Transaction.Spend(inpID)
187 resOut, err := template.Transaction.Output(*sp.SpentOutputId)
192 if segwit.IsP2WPKHScript(resOut.ControlProgram.Code) {
193 totalP2WPKHGas += baseP2WPKHGas
194 } else if segwit.IsP2WSHScript(resOut.ControlProgram.Code) {
195 sigInst := template.SigningInstructions[pos]
196 totalP2WSHGas += estimateP2WSHGas(sigInst)
200 // total estimate gas
201 totalGas := totalTxSizeGas + totalP2WPKHGas + totalP2WSHGas
203 // rounding totalNeu with base rate 100000
204 totalNeu := float64(totalGas*consensus.VMGasRate) / defaultBaseRate
205 roundingNeu := math.Ceil(totalNeu)
206 estimateNeu := int64(roundingNeu) * int64(defaultBaseRate)
210 return &EstimateTxGasResp{
211 TotalNeu: estimateNeu,
212 StorageNeu: totalTxSizeGas * consensus.VMGasRate,
213 VMNeu: (totalP2WPKHGas + totalP2WSHGas) * consensus.VMGasRate,
217 // estimate p2wsh gas.
218 // OP_CHECKMULTISIG consume (984 * a - 72 * b - 63) gas,
219 // where a represent the num of public keys, and b represent the num of quorum.
220 func estimateP2WSHGas(sigInst *txbuilder.SigningInstruction) int64 {
222 baseP2WSHGas := int64(738)
224 for _, witness := range sigInst.WitnessComponents {
225 switch t := witness.(type) {
226 case *txbuilder.SignatureWitness:
227 P2WSHGas += baseP2WSHGas + (984*int64(len(t.Keys)) - 72*int64(t.Quorum) - 63)
228 case *txbuilder.RawTxSigWitness:
229 P2WSHGas += baseP2WSHGas + (984*int64(len(t.Keys)) - 72*int64(t.Quorum) - 63)
235 // estimate signature part size.
236 // if need multi-sign, calculate the size according to the length of keys.
237 func estimateSignSize(signingInstructions []*txbuilder.SigningInstruction) int64 {
239 baseWitnessSize := int64(300)
241 for _, sigInst := range signingInstructions {
242 for _, witness := range sigInst.WitnessComponents {
243 switch t := witness.(type) {
244 case *txbuilder.SignatureWitness:
245 signSize += int64(t.Quorum) * baseWitnessSize
246 case *txbuilder.RawTxSigWitness:
247 signSize += int64(t.Quorum) * baseWitnessSize
254 // POST /estimate-transaction-gas
255 func (a *API) estimateTxGas(ctx context.Context, in struct {
256 TxTemplate txbuilder.Template `json:"transaction_template"`
258 txGasResp, err := EstimateTxGas(in.TxTemplate)
260 return NewErrorResponse(err)
262 return NewSuccessResponse(txGasResp)