package service import ( "context" "encoding/hex" "os" "os/exec" "strconv" "strings" "github.com/0glabs/0g-chain/helper/da/client" "github.com/0glabs/0g-chain/helper/da/types" "github.com/0glabs/0g-chain/helper/da/utils/sizedw8grp" jsoniter "github.com/json-iterator/go" "github.com/lesismal/nbio/nbhttp/websocket" "github.com/pkg/errors" "github.com/rs/zerolog/log" ) const ( defaultClientInstance = 10 ) var rpcClient client.DaLightRpcClient func OnMessage(ctx context.Context, c *websocket.Conn, messageType websocket.MessageType, data []byte) { if messageType == websocket.TextMessage { rawMsg := unwrapJsonRpc(data) if verifyQuery(rawMsg) { eventStr := jsoniter.Get(rawMsg, "events").ToString() events := map[string][]string{} if err := jsoniter.UnmarshalFromString(eventStr, &events); err == nil { dasRequestMap := make(map[string]string, 4) for key, val := range events { if strings.HasPrefix(key, "das_request.") { dasRequestMap[strings.ReplaceAll(key, "das_request.", "")] = val[0] } } if len(dasRequestMap) == 4 { rid, _ := strconv.ParseUint(dasRequestMap["request_id"], 10, 64) numBlobs, _ := strconv.ParseUint(dasRequestMap["num_blobs"], 10, 64) req := types.DASRequest{ RequestId: rid, StreamId: dasRequestMap["stream_id"], BatchHeaderHash: dasRequestMap["batch_header_hash"], NumBlobs: numBlobs, } err := handleDasRequest(ctx, req) if err != nil { log.Err(err).Msgf("failed to handle das request: %v, %v", req, err) } else { log.Info().Msgf("successfully handled das request: %v", req) } } } } } else { // TODO: handle other message } } func OnClose() { if rpcClient != nil { rpcClient.Destroy() rpcClient = nil } } func unwrapJsonRpc(data []byte) []byte { result := jsoniter.Get(data, "result") if 0 < len(result.Keys()) { return []byte(result.ToString()) } return []byte{} } func verifyQuery(data []byte) bool { if len(data) > 0 { return jsoniter.Get(data, "query").ToString() == "tm.event='Tx'" } return false } func min(a, b int) int { if a < b { return a } return b } func handleDasRequest(ctx context.Context, request types.DASRequest) error { if rpcClient == nil { addrVal := ctx.Value(types.DA_RPC_ADDRESS) if addrVal == nil { return errors.New("da light service address not found in context") } limit := ctx.Value(types.INSTANCE_LIMIT) if limit == nil { limit = defaultClientInstance } rpcClient = client.NewDaLightClient(addrVal.(string), limit.(int)) } streamID, err := hex.DecodeString(request.StreamId) if err != nil { return err } batchHeaderHash, err := hex.DecodeString(request.BatchHeaderHash) if err != nil { return err } result := make(chan bool, request.NumBlobs) taskCnt := min(rpcClient.GetInstanceCount(), int(request.NumBlobs)) wg := sizedw8grp.New(taskCnt) for i := uint64(0); i < request.NumBlobs; i++ { wg.Add() go func(idx uint64) { defer wg.Done() ret, err := rpcClient.Sample(ctx, streamID, batchHeaderHash, uint32(idx), 1) if err != nil { log.Err(err).Msgf("failed to sample data availability with blob index %d", idx) result <- false } else { log.Info().Msgf("sample result for blob index %d: %v", idx, ret) result <- ret } }(i) } wg.Wait() close(result) finalResult := true for val := range result { if !val { finalResult = false break } } return runEvmosdCliReportDasResult(ctx, request.RequestId, finalResult) } func runEvmosdCliReportDasResult(ctx context.Context, requestId uint64, result bool) error { relativePath := ctx.Value(types.NODE_CLI_RELATIVE_PATH) if relativePath == nil { return errors.New("relativePath not found in context") } account := ctx.Value(types.NODE_CLI_EXEC_ACCOUNT) if account == nil { return errors.New("account not found in context") } args := []string{ "tx", "das", "report-das-result", strconv.FormatUint(requestId, 10), strconv.FormatBool(result), "--from", account.(string), "--gas-prices", "7678500neuron", // TODO: use args to set gas prices } homePath := ctx.Value(types.NODE_HOME_PATH) if len(homePath.(string)) > 0 { args = append(args, "--home", homePath.(string)) } keyring := ctx.Value(types.NODE_CLI_EXEC_KEYRING) if len(keyring.(string)) > 0 { args = append(args, "--keyring-backend", keyring.(string)) } cmdStr := relativePath.(string) + "0gchaind" cmd := exec.Command(cmdStr, append(args, "-y")...) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr return cmd.Run() }