github.com/iotexproject/iotex-core@v1.14.1-rc1/ioctl/cmd/ws/wsproject.go (about)

     1  package ws
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"crypto/sha256"
     7  	_ "embed" // import ws project ABI
     8  	"encoding/hex"
     9  	"fmt"
    10  	"os"
    11  	"reflect"
    12  	"time"
    13  
    14  	"github.com/cenkalti/backoff"
    15  	"github.com/ethereum/go-ethereum/accounts/abi"
    16  	"github.com/ethereum/go-ethereum/common"
    17  	"github.com/grpc-ecosystem/go-grpc-middleware/util/metautils"
    18  	"github.com/iotexproject/go-pkgs/hash"
    19  	"github.com/iotexproject/iotex-proto/golang/iotexapi"
    20  	"github.com/iotexproject/iotex-proto/golang/iotextypes"
    21  	shell "github.com/ipfs/go-ipfs-api"
    22  	"github.com/pkg/errors"
    23  	"github.com/spf13/cobra"
    24  	"go.uber.org/zap"
    25  	"google.golang.org/grpc/codes"
    26  	"google.golang.org/grpc/status"
    27  
    28  	"github.com/iotexproject/iotex-core/ioctl/config"
    29  	"github.com/iotexproject/iotex-core/ioctl/output"
    30  	"github.com/iotexproject/iotex-core/ioctl/util"
    31  	"github.com/iotexproject/iotex-core/pkg/log"
    32  )
    33  
    34  var (
    35  	// wsProject represents the w3bstream project management command
    36  	wsProject = &cobra.Command{
    37  		Use:   "project",
    38  		Short: config.TranslateInLang(wsProjectShorts, config.UILanguage),
    39  	}
    40  
    41  	// wsProjectShorts w3bstream project shorts multi-lang support
    42  	wsProjectShorts = map[config.Language]string{
    43  		config.English: "w3bstream project management",
    44  		config.Chinese: "w3bstream项目管理",
    45  	}
    46  
    47  	wsProjectRegisterContractAddress string
    48  	wsProjectRegisterContractABI     abi.ABI
    49  
    50  	//go:embed wsproject.json
    51  	wsProjectRegisterContractJSONABI []byte
    52  	wsProjectIPFSEndpoint            string
    53  	wsProjectIPFSGatewayEndpoint     string
    54  )
    55  
    56  // Errors
    57  var (
    58  	errProjectConfigHashUnmatched = errors.New("project config hash unmatched")
    59  	errProjectConfigReadFailed    = errors.New("failed to read project config file")
    60  	errUploadProjectConfigFailed  = errors.New("failed to upload project config file")
    61  )
    62  
    63  // Constants
    64  const (
    65  	createWsProjectFuncName    = "createProject"
    66  	updateWsProjectFuncName    = "updateProject"
    67  	queryWsProjectFuncName     = "projects"
    68  	wsProjectUpsertedEventName = "ProjectUpserted"
    69  )
    70  
    71  type projectMeta struct {
    72  	ProjectID  uint64 `json:"projectID"`
    73  	URI        string `json:"uri"`
    74  	HashSha256 string `json:"hashSha256"`
    75  }
    76  
    77  func init() {
    78  	var err error
    79  	wsProjectRegisterContractABI, err = abi.JSON(bytes.NewReader(wsProjectRegisterContractJSONABI))
    80  	if err != nil {
    81  		log.L().Panic("cannot get abi JSON data", zap.Error(err))
    82  	}
    83  
    84  	wsProject.AddCommand(wsProjectCreate)
    85  	wsProject.AddCommand(wsProjectUpdate)
    86  	wsProject.AddCommand(wsProjectQuery)
    87  
    88  	wsProjectRegisterContractAddress = config.ReadConfig.WsRegisterContract
    89  	wsProjectIPFSEndpoint = config.ReadConfig.IPFSEndpoint
    90  	wsProjectIPFSGatewayEndpoint = config.ReadConfig.IPFSGateway
    91  }
    92  
    93  func convertStringToAbiBytes32(hash string) (interface{}, error) {
    94  	t, _ := abi.NewType("bytes32", "", nil)
    95  
    96  	bytecode, err := hex.DecodeString(util.TrimHexPrefix(hash))
    97  	if err != nil {
    98  		return nil, err
    99  	}
   100  
   101  	if t.Size != len(bytecode) {
   102  		return nil, errors.New("invalid arg")
   103  	}
   104  
   105  	bytesType := reflect.ArrayOf(t.Size, reflect.TypeOf(uint8(0)))
   106  	bytesVal := reflect.New(bytesType).Elem()
   107  
   108  	for i, b := range bytecode {
   109  		bytesVal.Index(i).Set(reflect.ValueOf(b))
   110  	}
   111  
   112  	return bytesVal.Interface(), nil
   113  }
   114  
   115  func waitReceiptByActionHash(h string) (*iotexapi.GetReceiptByActionResponse, error) {
   116  	conn, err := util.ConnectToEndpoint(config.ReadConfig.SecureConnect && !config.Insecure)
   117  	if err != nil {
   118  		return nil, output.NewError(output.NetworkError, "failed to connect to endpoint", err)
   119  	}
   120  	defer conn.Close()
   121  	cli := iotexapi.NewAPIServiceClient(conn)
   122  	ctx := context.Background()
   123  
   124  	jwtMD, err := util.JwtAuth()
   125  	if err == nil {
   126  		ctx = metautils.NiceMD(jwtMD).ToOutgoing(ctx)
   127  	}
   128  
   129  	var rsp *iotexapi.GetReceiptByActionResponse
   130  	err = backoff.Retry(func() error {
   131  		rsp, err = cli.GetReceiptByAction(ctx, &iotexapi.GetReceiptByActionRequest{
   132  			ActionHash: h,
   133  		})
   134  		return err
   135  	}, backoff.WithMaxRetries(backoff.NewConstantBackOff(30*time.Second), 3))
   136  	if err != nil {
   137  		sta, ok := status.FromError(err)
   138  		if ok && sta.Code() == codes.NotFound {
   139  			return nil, output.NewError(output.APIError, "not found", nil)
   140  		} else if ok {
   141  			return nil, output.NewError(output.APIError, sta.Message(), nil)
   142  		}
   143  		return nil, output.NewError(output.NetworkError, "failed to invoke GetReceiptByAction api", err)
   144  	}
   145  	return rsp, nil
   146  }
   147  
   148  func getEventInputsByName(logs []*iotextypes.Log, eventName string) (map[string]any, error) {
   149  	var (
   150  		abievent *abi.Event
   151  		log      *iotextypes.Log
   152  	)
   153  	for _, l := range logs {
   154  		evabi, err := wsProjectRegisterContractABI.EventByID(common.BytesToHash(l.Topics[0]))
   155  		if err != nil {
   156  			return nil, errors.Wrapf(err, "get event abi from topic %v failed", l.Topics[0])
   157  		}
   158  		if evabi.Name == eventName {
   159  			abievent, log = evabi, l
   160  			break
   161  		}
   162  	}
   163  
   164  	if abievent == nil || log == nil {
   165  		return nil, errors.Errorf("event not found: %s", eventName)
   166  	}
   167  
   168  	inputs := make(map[string]any)
   169  	if len(log.Data) > 0 {
   170  		if err := abievent.Inputs.UnpackIntoMap(inputs, log.Data); err != nil {
   171  			return nil, errors.Wrap(err, "unpack event data failed")
   172  		}
   173  	}
   174  	args := make(abi.Arguments, 0)
   175  	for _, arg := range abievent.Inputs {
   176  		if arg.Indexed {
   177  			args = append(args, arg)
   178  		}
   179  	}
   180  	topics := make([]common.Hash, 0)
   181  	for i, topic := range log.Topics {
   182  		if i > 0 {
   183  			topics = append(topics, common.BytesToHash(topic))
   184  		}
   185  	}
   186  	if err := abi.ParseTopicsIntoMap(inputs, args, topics); err != nil {
   187  		return nil, errors.Wrap(err, "unpack event indexed fields failed")
   188  	}
   189  
   190  	return inputs, nil
   191  }
   192  
   193  // upload content to endpoint, returns fetch url, content hash and error
   194  func upload(endpoint string, filename, hashstr string) (string, hash.Hash256, error) {
   195  	// read file content
   196  	content, err := os.ReadFile(filename)
   197  	if err != nil {
   198  		err = errors.Wrap(err, errProjectConfigReadFailed.Error())
   199  		return "", hash.ZeroHash256, err
   200  	}
   201  
   202  	// calculate and validate hash
   203  	hash256b := sha256.Sum256(content)
   204  	if hashstr != "" {
   205  		var hashInput hash.Hash256
   206  		hashInput, err = hash.HexStringToHash256(hashstr)
   207  		if err != nil {
   208  			return "", hash.ZeroHash256, err
   209  		}
   210  		if hashInput != hash256b {
   211  			return "", hash.ZeroHash256, errProjectConfigHashUnmatched
   212  		}
   213  	}
   214  
   215  	// upload content to ipfs endpoint
   216  	var (
   217  		sh  = shell.NewShell(endpoint)
   218  		cid string
   219  	)
   220  	cid, err = sh.Add(bytes.NewReader(content))
   221  	if err != nil {
   222  		return "", hash.ZeroHash256, errors.Wrap(err, errUploadProjectConfigFailed.Error())
   223  	}
   224  
   225  	err = sh.Pin(cid)
   226  	if err != nil {
   227  		return "", hash.ZeroHash256, errors.Wrap(err, errUploadProjectConfigFailed.Error())
   228  	}
   229  
   230  	return fmt.Sprintf("ipfs://%s/%s", wsProjectIPFSEndpoint, cid), hash256b, nil
   231  }