UNPKG

undedoloremque

Version:
775 lines (683 loc) 23.6 kB
import { UInt64Value } from '@bnb-chain/greenfield-cosmos-types/greenfield/common/wrapper'; import { ActionType, Principal, PrincipalType, principalTypeFromJSON, } from '@bnb-chain/greenfield-cosmos-types/greenfield/permission/common'; import { visibilityTypeFromJSON } from '@bnb-chain/greenfield-cosmos-types/greenfield/storage/common'; import { QueryBucketNFTResponse, QueryHeadBucketExtraResponse, QueryHeadBucketResponse, QueryNFTRequest, QueryPolicyForAccountRequest, QueryPolicyForAccountResponse, QueryVerifyPermissionResponse, } from '@bnb-chain/greenfield-cosmos-types/greenfield/storage/query'; import { MsgCreateBucket, MsgDeleteBucket, MsgDeletePolicy, MsgMigrateBucket, MsgPutPolicy, MsgUpdateBucketInfo, } from '@bnb-chain/greenfield-cosmos-types/greenfield/storage/tx'; import { ResourceTags } from '@bnb-chain/greenfield-cosmos-types/greenfield/storage/types'; import { bytesFromBase64 } from '@bnb-chain/greenfield-cosmos-types/helpers'; import { Headers } from 'cross-fetch'; import { bytesToUtf8, hexToBytes } from 'ethereum-cryptography/utils'; import Long from 'long'; import { container, delay, inject, injectable } from 'tsyringe'; import { GRNToString, MsgCreateBucketTypeUrl, MsgDeleteBucketTypeUrl, MsgMigrateBucketTypeUrl, MsgUpdateBucketInfoTypeUrl, newBucketGRN, TxResponse, } from '..'; import { RpcQueryClient } from '../clients/queryclient'; import { HTTPHeaderUserAddress } from '../clients/spclient/auth'; import { getApprovalMetaInfo } from '../clients/spclient/spApis/approval'; import { getBucketMetaInfo, parseGetBucketMetaResponse, } from '../clients/spclient/spApis/getBucketMeta'; import { getUserBucketMetaInfo, parseGetUserBucketsResponse, } from '../clients/spclient/spApis/getUserBuckets'; import { getListBucketReadRecordMetaInfo, parseListBucketReadRecordResponse, } from '../clients/spclient/spApis/listBucketReadRecords'; import { getListBucketsByIDsMetaInfo, parseListBucketsByIdsResponse, } from '../clients/spclient/spApis/listBucketsByIds'; import { getListBucketByPaymentMetaInfo, parseListBucketByPaymentResponse, } from '../clients/spclient/spApis/listBucketsByPayment'; import { parseError } from '../clients/spclient/spApis/parseError'; import { getQueryBucketReadQuotaMetaInfo, parseReadQuotaResponse, } from '../clients/spclient/spApis/queryBucketReadQuota'; import { AuthType, SpClient } from '../clients/spclient/spClient'; import { TxClient } from '../clients/txClient'; import { METHOD_GET, NORMAL_ERROR_CODE } from '../constants/http'; import { getMsgCreateBucketSDKTypeEIP712 } from '../messages/greenfield/storage/MsgCreateBucket'; import { MsgDeleteBucketSDKTypeEIP712 } from '../messages/greenfield/storage/MsgDeleteBucket'; import { MsgMigrateBucketSDKTypeEIP712 } from '../messages/greenfield/storage/MsgMigrateBucket'; import { MsgUpdateBucketInfoSDKTypeEIP712 } from '../messages/greenfield/storage/MsgUpdateBucketInfo'; import type { CreateBucketApprovalRequest, CreateBucketApprovalResponse, GetBucketMetaRequest, GetBucketMetaResponse, GetUserBucketsRequest, GetUserBucketsResponse, IQuotaProps, ListBucketReadRecordRequest, ListBucketReadRecordResponse, ListBucketsByIDsRequest, ListBucketsByIDsResponse, ListBucketsByPaymentAccountRequest, ListBucketsByPaymentAccountResponse, MigrateBucketApprovalRequest, MigrateBucketApprovalResponse, ReadQuotaRequest, SpResponse, } from '../types/sp'; import { decodeObjectFromHexString } from '../utils/encoding'; import { isValidAddress, isValidBucketName, isValidUrl } from '../utils/s3'; import { Sp } from './sp'; import { Storage } from './storage'; export interface IBucket { /** * get approval of creating bucket and send createBucket txn to greenfield chain */ createBucket(params: CreateBucketApprovalRequest, authType: AuthType): Promise<TxResponse>; deleteBucket(msg: MsgDeleteBucket): Promise<TxResponse>; deleteBucketPolicy( operator: string, bucketName: string, principalAddr: string, principalType: keyof typeof PrincipalType, ): Promise<TxResponse>; getBucketMeta(params: GetBucketMetaRequest): Promise<SpResponse<GetBucketMetaResponse>>; getBucketPolicy(request: QueryPolicyForAccountRequest): Promise<QueryPolicyForAccountResponse>; /** * return quota info of bucket of current month, include chain quota, free quota and consumed quota */ getBucketReadQuota( configParam: ReadQuotaRequest, authType: AuthType, ): Promise<SpResponse<IQuotaProps>>; /** * returns the signature info for the approval of preCreating resources */ getCreateBucketApproval( configParam: CreateBucketApprovalRequest, authType: AuthType, ): Promise<SpResponse<string>>; getMigrateBucketApproval( params: MigrateBucketApprovalRequest, authType: AuthType, ): Promise<SpResponse<string>>; /** * check if the permission of bucket is allowed to the user. */ getVerifyPermission( bucketName: string, operator: string, actionType: ActionType, ): Promise<QueryVerifyPermissionResponse>; /** * query the bucketInfo on chain, return the bucket info if exists */ headBucket(bucketName: string): Promise<QueryHeadBucketResponse>; /** * query the bucketInfo on chain by bucketId, return the bucket info if exists */ headBucketById(bucketId: string): Promise<QueryHeadBucketResponse>; headBucketExtra(bucketName: string): Promise<QueryHeadBucketExtraResponse>; headBucketNFT(request: QueryNFTRequest): Promise<QueryBucketNFTResponse>; listBucketReadRecords( params: ListBucketReadRecordRequest, authType: AuthType, ): Promise<SpResponse<ListBucketReadRecordResponse>>; listBuckets( configParam: GetUserBucketsRequest, ): Promise<SpResponse<GetUserBucketsResponse['GfSpGetUserBucketsResponse']['Buckets']>>; listBucketsByIds(params: ListBucketsByIDsRequest): Promise<SpResponse<ListBucketsByIDsResponse>>; /** * ListBucketsByPaymentAccount list buckets by payment account */ listBucketsByPaymentAccount( params: ListBucketsByPaymentAccountRequest, ): Promise<SpResponse<ListBucketsByPaymentAccountResponse>>; migrateBucket(params: MigrateBucketApprovalRequest, authType: AuthType): Promise<TxResponse>; putBucketPolicy(bucketName: string, srcMsg: Omit<MsgPutPolicy, 'resource'>): Promise<TxResponse>; /** * Update the bucket meta on chain, including read quota, payment address or visibility. It will send the MsgUpdateBucketInfo msg to greenfield to update the meta. */ updateBucketInfo( srcMsg: Omit<MsgUpdateBucketInfo, 'chargedReadQuota'> & { chargedReadQuota?: string }, ): Promise<TxResponse>; } @injectable() export class Bucket implements IBucket { constructor( @inject(delay(() => TxClient)) private txClient: TxClient, @inject(delay(() => Sp)) private sp: Sp, @inject(delay(() => Storage)) private storage: Storage, ) {} private queryClient = container.resolve(RpcQueryClient); private spClient = container.resolve(SpClient); public async getCreateBucketApproval( params: CreateBucketApprovalRequest, authType: AuthType, ): Promise<SpResponse<string>> { const { bucketName, creator, visibility = 'VISIBILITY_TYPE_PUBLIC_READ', chargedReadQuota, spInfo, duration, paymentAddress, tags, } = params; try { if (!spInfo.primarySpAddress) { throw new Error('Primary sp address is missing'); } if (!isValidBucketName(bucketName)) { throw new Error('Error bucket name'); } if (!creator) { throw new Error('Empty creator address'); } const endpoint = await this.sp.getSPUrlByPrimaryAddr(spInfo.primarySpAddress); const { reqMeta, optionsWithOutHeaders, url } = getApprovalMetaInfo<CreateBucketApprovalResponse>(endpoint, 'CreateBucket', { bucket_name: bucketName, creator, visibility, primary_sp_address: spInfo.primarySpAddress, primary_sp_approval: { expired_height: '0', sig: '', global_virtual_group_family_id: 0, }, charged_read_quota: chargedReadQuota, payment_address: paymentAddress, tags: tags, }); const signHeaders = await this.spClient.signHeaders(reqMeta, authType); const requestOptions: RequestInit = { ...optionsWithOutHeaders, headers: signHeaders, }; const result = await this.spClient.callApi(url, requestOptions, duration, { code: -1, message: 'Get create bucket approval error.', }); const signedMsgString = result.headers.get('X-Gnfd-Signed-Msg') || ''; return { code: 0, message: 'Get create bucket approval success.', body: signedMsgString, statusCode: result.status, } as SpResponse<string>; } catch (error: any) { throw { code: -1, message: error.message, statusCode: error?.statusCode || NORMAL_ERROR_CODE, }; } } private async createBucketTx(msg: MsgCreateBucket, signedMsg: CreateBucketApprovalResponse) { const isTagsEmpty = msg?.tags?.tags?.length === 0; const MsgCreateBucketSDKTypeEIP712 = getMsgCreateBucketSDKTypeEIP712(isTagsEmpty); return await this.txClient.tx( MsgCreateBucketTypeUrl, msg.creator, MsgCreateBucketSDKTypeEIP712, { ...signedMsg, type: MsgCreateBucketTypeUrl, charged_read_quota: signedMsg.charged_read_quota, visibility: signedMsg.visibility, primary_sp_approval: signedMsg.primary_sp_approval, }, MsgCreateBucket.encode(msg).finish(), ); } public async createBucket(params: CreateBucketApprovalRequest, authType: AuthType) { const { body } = await this.getCreateBucketApproval(params, authType); if (!body) { throw new Error('Get create bucket approval error'); } const signedMsg = JSON.parse(bytesToUtf8(hexToBytes(body))) as CreateBucketApprovalResponse; const msg: MsgCreateBucket = { bucketName: signedMsg.bucket_name, creator: signedMsg.creator, visibility: visibilityTypeFromJSON(signedMsg.visibility), primarySpAddress: signedMsg.primary_sp_address, primarySpApproval: { expiredHeight: Long.fromString(signedMsg.primary_sp_approval.expired_height), sig: bytesFromBase64(signedMsg.primary_sp_approval.sig), globalVirtualGroupFamilyId: signedMsg.primary_sp_approval.global_virtual_group_family_id, }, chargedReadQuota: Long.fromString(signedMsg.charged_read_quota), paymentAddress: signedMsg.payment_address, tags: ResourceTags.fromJSON(signedMsg.tags), }; return await this.createBucketTx(msg, signedMsg); } public async deleteBucket(msg: MsgDeleteBucket) { return await this.txClient.tx( MsgDeleteBucketTypeUrl, msg.operator, MsgDeleteBucketSDKTypeEIP712, MsgDeleteBucket.toSDK(msg), MsgDeleteBucket.encode(msg).finish(), ); } public async headBucket(bucketName: string) { const rpc = await this.queryClient.getStorageQueryClient(); return await rpc.HeadBucket({ bucketName, }); } public async headBucketById(bucketId: string) { const rpc = await this.queryClient.getStorageQueryClient(); return await rpc.HeadBucketById({ bucketId, }); } public async headBucketExtra(bucketName: string) { const rpc = await this.queryClient.getStorageQueryClient(); return await rpc.HeadBucketExtra({ bucketName, }); } public async headBucketNFT(request: QueryNFTRequest) { const rpc = await this.queryClient.getStorageQueryClient(); return await rpc.HeadBucketNFT(request); } public async getVerifyPermission(bucketName: string, operator: string, actionType: ActionType) { const rpc = await this.queryClient.getStorageQueryClient(); return rpc.VerifyPermission({ bucketName, operator, objectName: '', actionType, }); } public async listBuckets(configParam: GetUserBucketsRequest) { try { const { address, duration = 30000, endpoint } = configParam; if (!isValidAddress(address)) { throw new Error('Error address'); } if (!isValidUrl(endpoint)) { throw new Error('Invalid endpoint'); } const { url } = getUserBucketMetaInfo(endpoint); const headers = new Headers({ [HTTPHeaderUserAddress]: address, }); const result = await this.spClient.callApi( url, { headers, method: METHOD_GET, }, duration, ); const { status } = result; if (!result.ok) { const xmlError = await result.text(); const { code, message } = await parseError(xmlError); throw { code: code || -1, message: message || 'Get bucket error.', statusCode: status, }; } const xmlData = await result.text(); const res = await parseGetUserBucketsResponse(xmlData); return { code: 0, message: 'Get bucket success.', statusCode: status, body: res.GfSpGetUserBucketsResponse.Buckets, }; } catch (error: any) { return { code: -1, message: error.message, statusCode: error?.statusCode || NORMAL_ERROR_CODE, }; } } public async getBucketReadQuota( params: ReadQuotaRequest, authType: AuthType, ): Promise<SpResponse<IQuotaProps>> { try { const { bucketName, duration = 30000 } = params; if (!isValidBucketName(bucketName)) { throw new Error('Error bucket name'); } let endpoint = params.endpoint; if (!endpoint) { endpoint = await this.sp.getSPUrlByBucket(bucketName); } const { url, optionsWithOutHeaders, reqMeta } = await getQueryBucketReadQuotaMetaInfo( endpoint, params, ); const signHeaders = await this.spClient.signHeaders(reqMeta, authType); const result = await this.spClient.callApi( url, { ...optionsWithOutHeaders, headers: signHeaders, }, duration, { code: -1, message: 'Get Bucket Quota error.', }, ); const xmlData = await result.text(); const res = await parseReadQuotaResponse(xmlData); return { code: 0, body: { readQuota: Number(res.GetReadQuotaResult.ReadQuotaSize ?? '0'), freeQuota: Number(res.GetReadQuotaResult.SPFreeReadQuotaSize ?? '0'), consumedQuota: Number(res.GetReadQuotaResult.ReadConsumedSize ?? '0'), freeConsumedSize: Number(res.GetReadQuotaResult.FreeConsumedSize ?? '0'), }, message: 'Get bucket read quota.', statusCode: result.status, }; } catch (error: any) { return { code: -1, message: error.message, statusCode: error?.statusCode || NORMAL_ERROR_CODE, }; } } public async updateBucketInfo( srcMsg: Omit<MsgUpdateBucketInfo, 'chargedReadQuota'> & { chargedReadQuota: string }, ) { const msg: MsgUpdateBucketInfo = { ...srcMsg, visibility: visibilityTypeFromJSON(srcMsg.visibility), chargedReadQuota: UInt64Value.fromPartial({ value: Long.fromString(srcMsg.chargedReadQuota), }), }; return await this.txClient.tx( MsgUpdateBucketInfoTypeUrl, msg.operator, MsgUpdateBucketInfoSDKTypeEIP712, { ...MsgUpdateBucketInfo.toSDK(msg), charged_read_quota: { value: srcMsg.chargedReadQuota, }, }, MsgUpdateBucketInfo.encode(msg).finish(), ); } public async putBucketPolicy(bucketName: string, srcMsg: Omit<MsgPutPolicy, 'resource'>) { const resource = GRNToString(newBucketGRN(bucketName)); const msg: MsgPutPolicy = { ...srcMsg, resource, }; return this.storage.putPolicy(msg); } public async deleteBucketPolicy( operator: string, bucketName: string, principalAddr: string, principalType: keyof typeof PrincipalType, ) { const resource = GRNToString(newBucketGRN(bucketName)); const principal: Principal = { type: principalTypeFromJSON(principalType), value: principalAddr, }; const msg: MsgDeletePolicy = { resource, principal, operator: operator, }; return await this.storage.deletePolicy(msg); } public async getBucketPolicy(request: QueryPolicyForAccountRequest) { const rpc = await this.queryClient.getStorageQueryClient(); return rpc.QueryPolicyForAccount(request); } public async getMigrateBucketApproval(params: MigrateBucketApprovalRequest, authType: AuthType) { const { bucketName, operator, dstPrimarySpId } = params; try { let endpoint = params.endpoint; if (!endpoint) { endpoint = await this.sp.getSPUrlById(params.dstPrimarySpId); } const { reqMeta, optionsWithOutHeaders, url } = getApprovalMetaInfo<MigrateBucketApprovalResponse>(endpoint, 'MigrateBucket', { operator: operator, bucket_name: bucketName, dst_primary_sp_id: dstPrimarySpId, dst_primary_sp_approval: { expired_height: '0', sig: '', global_virtual_group_family_id: 0, }, }); const signHeaders = await this.spClient.signHeaders(reqMeta, authType); const result = await this.spClient.callApi( url, { ...optionsWithOutHeaders, headers: signHeaders, }, 30000, ); const signedMsgString = result.headers.get('X-Gnfd-Signed-Msg') || ''; const signedMsg = decodeObjectFromHexString(signedMsgString) as MigrateBucketApprovalResponse; return { code: 0, message: 'Get migrate bucket approval success.', body: signedMsgString, statusCode: result.status, signedMsg: signedMsg, }; } catch (error: any) { throw { code: -1, message: error.message, statusCode: error?.statusCode || NORMAL_ERROR_CODE, }; } } public async migrateBucket(params: MigrateBucketApprovalRequest, authType: AuthType) { const { signedMsg } = await this.getMigrateBucketApproval(params, authType); if (!signedMsg) { throw new Error('Get migrate bucket approval error'); } const msg: MsgMigrateBucket = { bucketName: signedMsg.bucket_name, operator: signedMsg.operator, dstPrimarySpId: signedMsg.dst_primary_sp_id, dstPrimarySpApproval: { expiredHeight: Long.fromString(signedMsg.dst_primary_sp_approval.expired_height), globalVirtualGroupFamilyId: signedMsg.dst_primary_sp_approval.global_virtual_group_family_id, sig: bytesFromBase64(signedMsg.dst_primary_sp_approval.sig), }, }; return await this.migrateBucketTx(msg, signedMsg); } private async migrateBucketTx(msg: MsgMigrateBucket, signedMsg: MigrateBucketApprovalResponse) { return await this.txClient.tx( MsgMigrateBucketTypeUrl, msg.operator, MsgMigrateBucketSDKTypeEIP712, { ...signedMsg, type: MsgMigrateBucketTypeUrl, primary_sp_approval: { expired_height: signedMsg.dst_primary_sp_approval.expired_height, global_virtual_group_family_id: signedMsg.dst_primary_sp_approval.global_virtual_group_family_id, sig: signedMsg.dst_primary_sp_approval.sig, }, }, MsgMigrateBucket.encode(msg).finish(), ); } public async getBucketMeta(params: GetBucketMetaRequest) { const { bucketName } = params; if (!isValidBucketName(bucketName)) { throw new Error('Error bucket name'); } let endpoint = params.endpoint; if (!endpoint) { endpoint = await this.sp.getSPUrlByBucket(bucketName); } const { url } = getBucketMetaInfo(endpoint, params); const result = await this.spClient.callApi(url, { method: METHOD_GET, }); const xml = await result.text(); const res = await parseGetBucketMetaResponse(xml); return { code: 0, message: 'get bucket meta success.', statusCode: result.status, body: res, }; } public async listBucketReadRecords(params: ListBucketReadRecordRequest, authType: AuthType) { try { const { bucketName } = params; let endpoint = params.endpoint; if (!endpoint) { endpoint = await this.sp.getSPUrlByBucket(bucketName); } if (!isValidUrl(endpoint)) { throw new Error('Invalid endpoint'); } const { url, optionsWithOutHeaders, reqMeta } = getListBucketReadRecordMetaInfo( endpoint, params, ); const signHeaders = await this.spClient.signHeaders(reqMeta, authType); const result = await this.spClient.callApi( url, { ...optionsWithOutHeaders, headers: signHeaders, }, 3000, { code: -1, message: 'Get Bucket Quota error.', }, ); const xmlData = await result.text(); const res = await parseListBucketReadRecordResponse(xmlData); return { code: 0, body: res, message: 'success', statusCode: result.status, }; } catch (error: any) { return { code: -1, message: error.message, statusCode: error?.statusCode || NORMAL_ERROR_CODE, }; } } public async listBucketsByIds(params: ListBucketsByIDsRequest) { try { const { ids } = params; const sp = await this.sp.getInServiceSP(); const { url } = getListBucketsByIDsMetaInfo(sp.endpoint, { ids }); const result = await this.spClient.callApi( url, { headers: {}, method: METHOD_GET, }, 3000, ); const { status } = result; if (!result.ok) { const xmlError = await result.text(); const { code, message } = await parseError(xmlError); throw { code: code || -1, message: message || 'error', statusCode: status, }; } const xmlData = await result.text(); const res = await parseListBucketsByIdsResponse(xmlData); return { code: 0, message: 'success', statusCode: status, body: res, }; } catch (error: any) { return { code: -1, message: error.message, statusCode: error?.statusCode || NORMAL_ERROR_CODE, }; } } public async listBucketsByPaymentAccount(params: ListBucketsByPaymentAccountRequest) { try { const sp = await this.sp.getInServiceSP(); const { url } = getListBucketByPaymentMetaInfo(sp.endpoint, params); const result = await this.spClient.callApi(url, { headers: {}, method: METHOD_GET, }); const xmlData = await result.text(); const res = parseListBucketByPaymentResponse(xmlData); return { code: 0, message: 'Get bucket success.', statusCode: result.status, body: res, }; } catch (error: any) { return { code: -1, message: error.message, statusCode: error?.statusCode || NORMAL_ERROR_CODE, }; } } }