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 }