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  }