github.com/machinefi/w3bstream@v1.6.5-rc9.0.20240426031326-b8c7c4876e72/pkg/modules/vm/wasmapi/handler/send_tx.go (about)

     1  package handler
     2  
     3  import (
     4  	"context"
     5  	"net/http"
     6  	"time"
     7  
     8  	solclient "github.com/blocto/solana-go-sdk/client"
     9  	"github.com/blocto/solana-go-sdk/rpc"
    10  	"github.com/gin-gonic/gin"
    11  	"github.com/gin-gonic/gin/binding"
    12  	"github.com/pkg/errors"
    13  
    14  	"github.com/machinefi/w3bstream/pkg/depends/kit/logr"
    15  	"github.com/machinefi/w3bstream/pkg/enums"
    16  	"github.com/machinefi/w3bstream/pkg/models"
    17  	"github.com/machinefi/w3bstream/pkg/modules/operator"
    18  	"github.com/machinefi/w3bstream/pkg/types"
    19  	"github.com/machinefi/w3bstream/pkg/types/wasm"
    20  )
    21  
    22  type sendTxReq struct {
    23  	ChainName    enums.ChainName `json:"chainName"                 binding:"required"`
    24  	To           string          `json:"to,omitempty"`
    25  	Value        string          `json:"value,omitempty"`
    26  	Data         string          `json:"data"                      binding:"required"`
    27  	OperatorName string          `json:"operatorName,omitempty"`
    28  }
    29  
    30  type sendTxResp struct {
    31  	TransactionID types.SFID             `json:"transactionID,omitempty"`
    32  	State         enums.TransactionState `json:"state,omitempty"`
    33  	Timeout       bool                   `json:"timeout,omitempty"`
    34  }
    35  
    36  func (h *Handler) SendTx(c *gin.Context) {
    37  	// l := types.MustLoggerFromContext(c.Request.Context())
    38  	_, l := logr.Start(c, "vm.Handler.SendTx")
    39  	defer l.End()
    40  
    41  	var req sendTxReq
    42  	if err := c.ShouldBindBodyWith(&req, binding.JSON); err != nil {
    43  		l.Error(errors.Wrap(err, "decode http request failed"))
    44  		c.JSON(http.StatusBadRequest, newErrResp(err))
    45  		return
    46  	}
    47  
    48  	chain, ok := h.chainConf.Chains[req.ChainName]
    49  	if !ok {
    50  		err := errors.New("blockchain not exist")
    51  		l.Error(err)
    52  		c.JSON(http.StatusBadRequest, newErrResp(err))
    53  		return
    54  	}
    55  
    56  	prj := types.MustProjectFromContext(c.Request.Context())
    57  	l = l.WithValues("ProjectID", prj.ProjectID)
    58  
    59  	eventType := c.Request.Header.Get("eventType")
    60  
    61  	id := h.sfid.MustGenSFID()
    62  	l = l.WithValues("TransactionID", id)
    63  
    64  	m := &models.Transaction{
    65  		RelTransaction: models.RelTransaction{TransactionID: id},
    66  		RelProject:     models.RelProject{ProjectID: prj.ProjectID},
    67  		TransactionInfo: models.TransactionInfo{
    68  			ChainName:    chain.Name,
    69  			State:        enums.TRANSACTION_STATE__INIT,
    70  			EventType:    eventType,
    71  			Receiver:     req.To,
    72  			Value:        req.Value,
    73  			Data:         req.Data,
    74  			OperatorName: req.OperatorName,
    75  		},
    76  	}
    77  	if err := m.Create(h.mgrDB); err != nil {
    78  		l.Error(errors.Wrap(err, "create transaction db failed"))
    79  		c.JSON(http.StatusInternalServerError, newErrResp(err))
    80  		return
    81  	}
    82  
    83  	c.Request.Header.Add("TransactionID", id.String())
    84  	if err := h.setAsync(c); err != nil {
    85  		l.Error(err)
    86  		c.JSON(http.StatusInternalServerError, newErrResp(err))
    87  		return
    88  	}
    89  
    90  	c.JSON(http.StatusOK, &sendTxResp{TransactionID: id})
    91  }
    92  
    93  func (h *Handler) SendTxAsync(c *gin.Context) {
    94  	// l := types.MustLoggerFromContext(c.Request.Context())
    95  	_, l := logr.Start(c, "vm.Handler.SendTxAsync")
    96  	defer l.End()
    97  
    98  	chainCli := wasm.MustChainClientFromContext(c.Request.Context())
    99  
   100  	var req sendTxReq
   101  	var id types.SFID
   102  	c.ShouldBindBodyWith(&req, binding.JSON)
   103  	id.UnmarshalText([]byte(c.Request.Header.Get("TransactionID")))
   104  
   105  	prj := types.MustProjectFromContext(c.Request.Context())
   106  	if req.OperatorName == "" {
   107  		req.OperatorName = operator.DefaultOperatorName
   108  	}
   109  
   110  	l = l.WithValues("ProjectID", prj.ProjectID).WithValues("TransactionID", id)
   111  	var state enums.TransactionState
   112  	txResp, err := chainCli.SendTXWithOperator(h.chainConf, 0, req.ChainName, req.To, req.Value, req.Data, req.OperatorName, h.opPool, prj)
   113  	if err != nil {
   114  		state = enums.TRANSACTION_STATE__FAILED
   115  		l.Error(errors.Wrap(err, "send tx with operator failed"))
   116  	} else {
   117  		state = enums.TRANSACTION_STATE__PENDING
   118  	}
   119  
   120  	txInfo := models.TransactionInfo{
   121  		State: state,
   122  	}
   123  	if txResp != nil {
   124  		txInfo.Sender = txResp.Sender
   125  		txInfo.Hash = txResp.Hash
   126  		txInfo.Nonce = txResp.Nonce
   127  	}
   128  
   129  	m := &models.Transaction{
   130  		RelTransaction:  models.RelTransaction{TransactionID: id},
   131  		TransactionInfo: txInfo,
   132  	}
   133  	if err := m.UpdateByTransactionID(h.mgrDB); err != nil {
   134  		l.Error(errors.Wrap(err, "update transaction db failed"))
   135  		c.JSON(http.StatusInternalServerError, newErrResp(err))
   136  		return
   137  	}
   138  
   139  	if state == enums.TRANSACTION_STATE__FAILED {
   140  		c.JSON(http.StatusOK, &sendTxResp{TransactionID: id, State: state})
   141  		return
   142  	}
   143  
   144  	if err := h.setAsyncAdvance(c, "/system/send_tx/async/state", 10*time.Second); err != nil {
   145  		l.Error(err)
   146  		c.JSON(http.StatusInternalServerError, newErrResp(err))
   147  		return
   148  	}
   149  
   150  	c.Status(http.StatusNoContent)
   151  }
   152  
   153  func (h *Handler) SendTxAsyncStateCheck(c *gin.Context) {
   154  	// l := types.MustLoggerFromContext(c.Request.Context())
   155  	_, l := logr.Start(c, "vm.Handler.SendTxAsyncStateCheck")
   156  	defer l.End()
   157  
   158  	var req sendTxReq
   159  	var id types.SFID
   160  	c.ShouldBindBodyWith(&req, binding.JSON)
   161  	id.UnmarshalText([]byte(c.Request.Header.Get("TransactionID")))
   162  	l = l.WithValues("TransactionID", id)
   163  
   164  	m := &models.Transaction{
   165  		RelTransaction: models.RelTransaction{TransactionID: id},
   166  	}
   167  	if err := m.FetchByTransactionID(h.mgrDB); err != nil {
   168  		l.Error(errors.Wrap(err, "fetch by transaction id failed"))
   169  		c.JSON(http.StatusInternalServerError, newErrResp(err))
   170  		return
   171  	}
   172  
   173  	if time.Since(m.CreatedAt.Time) > 2*time.Hour {
   174  		l.Error(errors.New("transaction timeout"))
   175  		c.JSON(http.StatusOK, &sendTxResp{TransactionID: id, Timeout: true})
   176  		return
   177  	}
   178  
   179  	chain := h.chainConf.Chains[m.ChainName]
   180  
   181  	state := m.State
   182  	var err error
   183  	if chain.IsSolana() {
   184  		state, err = h.getSolanaState(chain, m.Hash)
   185  	} else {
   186  		state, err = h.getEthState(chain, m.Hash)
   187  	}
   188  	if err != nil {
   189  		l.Error(errors.Wrap(err, "get transaction state failed"))
   190  		c.JSON(http.StatusInternalServerError, newErrResp(err))
   191  		return
   192  	}
   193  
   194  	if state != m.State {
   195  		m.State = state
   196  		if err := m.UpdateByTransactionID(h.mgrDB); err != nil {
   197  			l.Error(errors.Wrap(err, "update db failed"))
   198  			c.JSON(http.StatusInternalServerError, newErrResp(err))
   199  			return
   200  		}
   201  	}
   202  
   203  	if state == enums.TRANSACTION_STATE__FAILED || state == enums.TRANSACTION_STATE__CONFIRMED {
   204  		c.JSON(http.StatusOK, &sendTxResp{TransactionID: id, State: state})
   205  		return
   206  	}
   207  
   208  	if err := h.setAsyncAdvance(c, "/system/send_tx/async/state", 10*time.Second); err != nil {
   209  		l.Error(err)
   210  		c.JSON(http.StatusInternalServerError, newErrResp(err))
   211  		return
   212  	}
   213  
   214  	c.Status(http.StatusNoContent)
   215  }
   216  
   217  func (h *Handler) getEthState(chain *types.Chain, hash string) (enums.TransactionState, error) {
   218  	return wasm.NewEthClient(chain).TransactionState(context.Background(), hash)
   219  }
   220  
   221  func (h *Handler) getSolanaState(chain *types.Chain, hash string) (enums.TransactionState, error) {
   222  	cli := solclient.NewClient(chain.Endpoint)
   223  	status, err := cli.GetSignatureStatus(context.Background(), hash)
   224  	if err != nil {
   225  		return enums.TRANSACTION_STATE_UNKNOWN, errors.Wrap(err, "query solana transaction failed")
   226  	}
   227  	switch *status.ConfirmationStatus {
   228  	case rpc.CommitmentProcessed:
   229  		return enums.TRANSACTION_STATE__PENDING, nil
   230  	case rpc.CommitmentConfirmed:
   231  		return enums.TRANSACTION_STATE__IN_BLOCK, nil
   232  	case rpc.CommitmentFinalized:
   233  		return enums.TRANSACTION_STATE__CONFIRMED, nil
   234  	}
   235  	return enums.TRANSACTION_STATE_UNKNOWN, errors.New("get solana transaction status failed")
   236  }