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 }