9 log "github.com/sirupsen/logrus"
11 "github.com/bytom/account"
12 "github.com/bytom/blockchain/txbuilder"
13 "github.com/bytom/consensus"
14 "github.com/bytom/consensus/segwit"
15 "github.com/bytom/errors"
16 "github.com/bytom/net/http/reqid"
17 "github.com/bytom/protocol/bc"
18 "github.com/bytom/protocol/bc/types"
19 "github.com/bytom/protocol/vm/vmutil"
23 defaultTxTTL = 30 * time.Minute
24 defaultBaseRate = float64(100000)
27 func (a *API) actionDecoder(action string) (func([]byte) (txbuilder.Action, error), bool) {
28 decoders := map[string]func([]byte) (txbuilder.Action, error){
29 "control_address": txbuilder.DecodeControlAddressAction,
30 "control_program": txbuilder.DecodeControlProgramAction,
31 "issue": a.wallet.AssetReg.DecodeIssueAction,
32 "retire": txbuilder.DecodeRetireAction,
33 "spend_account": a.wallet.AccountMgr.DecodeSpendAction,
34 "spend_account_unspent_output": a.wallet.AccountMgr.DecodeSpendUTXOAction,
36 decoder, ok := decoders[action]
40 func onlyHaveInputActions(req *BuildRequest) (bool, error) {
42 for i, act := range req.Actions {
43 actionType, ok := act["type"].(string)
45 return false, errors.WithDetailf(ErrBadActionType, "no action type provided on action %d", i)
48 if strings.HasPrefix(actionType, "spend") || actionType == "issue" {
53 return count == len(req.Actions), nil
56 func (a *API) buildSingle(ctx context.Context, req *BuildRequest) (*txbuilder.Template, error) {
57 if err := a.checkRequestValidity(ctx, req); err != nil {
60 actions, err := a.mergeSpendActions(req)
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.
71 for i, innerErr := range errors.Data(err)["actions"].([]error) {
73 rootErr = errors.Root(innerErr)
75 Errs = Errs + innerErr.Error()
77 err = errors.WithDetail(rootErr, Errs)
83 // ensure null is never returned for signing instructions
84 if tpl.SigningInstructions == nil {
85 tpl.SigningInstructions = []*txbuilder.SigningInstruction{}
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)
95 return NewErrorResponse(err)
98 return NewSuccessResponse(tmpl)
100 func (a *API) checkRequestValidity(ctx context.Context, req *BuildRequest) error {
101 if err := a.completeMissingIDs(ctx, req); err != nil {
105 if req.TTL.Duration == 0 {
106 req.TTL.Duration = defaultTxTTL
109 if ok, err := onlyHaveInputActions(req); err != nil {
112 return errors.WithDetail(ErrBadActionConstruction, "transaction contains only input actions and no output actions")
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)
122 return nil, errors.WithDetailf(ErrBadActionType, "no action type provided on action %d", i)
124 decoder, ok := a.actionDecoder(typ)
126 return nil, errors.WithDetailf(ErrBadActionType, "unknown action type %q on action %d", typ, i)
129 // Remarshal to JSON, the action may have been modified when we
131 b, err := json.Marshal(act)
135 action, err := decoder(b)
137 return nil, errors.WithDetailf(ErrBadAction, "%s on action %d", err.Error(), i)
139 actions = append(actions, action)
141 actions = account.MergeSpendAction(actions)
145 func (a *API) buildTxs(ctx context.Context, req *BuildRequest) ([]*txbuilder.Template, error) {
146 if err := a.checkRequestValidity(ctx, req); err != nil {
149 actions, err := a.mergeSpendActions(req)
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)
160 err = action.Build(ctx, builder)
169 tpl, _, err := builder.Build()
175 tpls = append(tpls, tpl)
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)
184 return NewErrorResponse(err)
186 return NewSuccessResponse(tmpls)
189 type submitTxResp struct {
190 TxID *bc.Hash `json:"tx_id"`
193 // POST /submit-transaction
194 func (a *API) submit(ctx context.Context, ins struct {
195 Tx types.Tx `json:"raw_transaction"`
197 if err := txbuilder.FinalizeTx(ctx, a.chain, &ins.Tx); err != nil {
198 return NewErrorResponse(err)
201 log.WithField("tx_id", ins.Tx.ID.String()).Info("submit single tx")
202 return NewSuccessResponse(&submitTxResp{TxID: &ins.Tx.ID})
205 type submitTxsResp struct {
206 TxID []*bc.Hash `json:"tx_id"`
209 // POST /submit-transactions
210 func (a *API) submitTxs(ctx context.Context, ins struct {
211 Tx []types.Tx `json:"raw_transactions"`
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)
218 log.WithField("tx_id", ins.Tx[i].ID.String()).Info("submit single tx")
219 txHashs = append(txHashs, &ins.Tx[i].ID)
221 return NewSuccessResponse(&submitTxsResp{TxID: txHashs})
224 // EstimateTxGasResp estimate transaction consumed gas
225 type EstimateTxGasResp struct {
226 TotalNeu int64 `json:"total_neu"`
227 FlexibleNeu int64 `json:"flexible_neu"`
228 StorageNeu int64 `json:"storage_neu"`
229 VMNeu int64 `json:"vm_neu"`
232 // estimateTxGas estimate consumed neu for transaction
233 func estimateTxGas(template txbuilder.Template) (*EstimateTxGasResp, error) {
234 var baseP2WSHSize, totalWitnessSize, baseP2WSHGas, totalP2WPKHGas, totalP2WSHGas, totalIssueGas int64
235 baseSize := int64(176) // inputSize(112) + outputSize(64)
236 baseP2WPKHSize := int64(98)
237 baseP2WPKHGas := int64(1409)
238 for pos, input := range template.Transaction.TxData.Inputs {
239 switch input.InputType() {
240 case types.SpendInputType:
241 controlProgram := input.ControlProgram()
242 if segwit.IsP2WPKHScript(controlProgram) {
243 totalWitnessSize += baseP2WPKHSize
244 totalP2WPKHGas += baseP2WPKHGas
245 } else if segwit.IsP2WSHScript(controlProgram) {
246 baseP2WSHSize, baseP2WSHGas = estimateP2WSHGas(template.SigningInstructions[pos])
247 totalWitnessSize += baseP2WSHSize
248 totalP2WSHGas += baseP2WSHGas
251 case types.IssuanceInputType:
252 issuanceProgram := input.IssuanceProgram()
253 if height, _ := vmutil.GetIssuanceProgramRestrictHeight(issuanceProgram); height > 0 {
254 // the gas for issue program with checking block height
257 baseIssueSize, baseIssueGas := estimateIssueGas(template.SigningInstructions[pos])
258 totalWitnessSize += baseIssueSize
259 totalIssueGas += baseIssueGas
263 flexibleGas := int64(0)
264 if totalP2WPKHGas > 0 {
265 flexibleGas += baseP2WPKHGas + (baseSize+baseP2WPKHSize)*consensus.StorageGasRate
266 } else if totalP2WSHGas > 0 {
267 flexibleGas += baseP2WSHGas + (baseSize+baseP2WSHSize)*consensus.StorageGasRate
268 } else if totalIssueGas > 0 {
269 totalIssueGas += baseP2WPKHGas
270 totalWitnessSize += baseSize + baseP2WPKHSize
273 // the total transaction storage gas
274 totalTxSizeGas := (int64(template.Transaction.TxData.SerializedSize) + totalWitnessSize) * consensus.StorageGasRate
276 // the total transaction gas is composed of storage and virtual machines
277 totalGas := totalTxSizeGas + totalP2WPKHGas + totalP2WSHGas + totalIssueGas + flexibleGas
278 return &EstimateTxGasResp{
279 TotalNeu: totalGas * consensus.VMGasRate,
280 FlexibleNeu: flexibleGas * consensus.VMGasRate,
281 StorageNeu: totalTxSizeGas * consensus.VMGasRate,
282 VMNeu: (totalP2WPKHGas + totalP2WSHGas + totalIssueGas) * consensus.VMGasRate,
286 // estimateP2WSH return the witness size and the gas consumed to execute the virtual machine for P2WSH program
287 func estimateP2WSHGas(sigInst *txbuilder.SigningInstruction) (int64, int64) {
288 var witnessSize, gas int64
289 for _, witness := range sigInst.WitnessComponents {
290 switch t := witness.(type) {
291 case *txbuilder.SignatureWitness:
292 witnessSize += 33*int64(len(t.Keys)) + 65*int64(t.Quorum)
293 gas += 1131*int64(len(t.Keys)) + 72*int64(t.Quorum) + 659
294 if int64(len(t.Keys)) == 1 && int64(t.Quorum) == 1 {
297 case *txbuilder.RawTxSigWitness:
298 witnessSize += 33*int64(len(t.Keys)) + 65*int64(t.Quorum)
299 gas += 1131*int64(len(t.Keys)) + 72*int64(t.Quorum) + 659
300 if int64(len(t.Keys)) == 1 && int64(t.Quorum) == 1 {
305 return witnessSize, gas
308 // estimateIssueGas return the witness size and the gas consumed to execute the virtual machine for issuance program
309 func estimateIssueGas(sigInst *txbuilder.SigningInstruction) (int64, int64) {
310 var witnessSize, gas int64
311 for _, witness := range sigInst.WitnessComponents {
312 switch t := witness.(type) {
313 case *txbuilder.SignatureWitness:
314 witnessSize += 65 * int64(t.Quorum)
315 gas += 1065*int64(len(t.Keys)) + 72*int64(t.Quorum) + 316
316 if int64(len(t.Keys)) == 1 && int64(t.Quorum) == 1 {
319 case *txbuilder.RawTxSigWitness:
320 witnessSize += 65 * int64(t.Quorum)
321 gas += 1065*int64(len(t.Keys)) + 72*int64(t.Quorum) + 316
322 if int64(len(t.Keys)) == 1 && int64(t.Quorum) == 1 {
327 return witnessSize, gas
330 // POST /estimate-transaction-gas
331 func (a *API) estimateTxGas(ctx context.Context, in struct {
332 TxTemplate txbuilder.Template `json:"transaction_template"`
334 txGasResp, err := estimateTxGas(in.TxTemplate)
336 return NewErrorResponse(err)
338 return NewSuccessResponse(txGasResp)