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  }