From 9a511bf864c922d689a51f1fcbe23274a246cf9d Mon Sep 17 00:00:00 2001 From: Chris Li Date: Wed, 23 Aug 2023 11:49:36 +0800 Subject: [PATCH 1/4] feat: improve migrate test (#135) * feat: add empty bucket test * fix: lint * fix: update sp version * fix: update sp version * fix: add challenge for bucket migrate * fix: add challenge for bucket migrate * fix: add challenge for bucket migrate * fix: fix sp version * fix: add CheckChallenge sleep --- .github/workflows/e2e.yml | 2 +- client/api_bucket.go | 20 ++++ e2e/basesuite/suite.go | 18 ++- e2e/e2e_migrate_bucket_test.go | 197 ++++++++++++++++++++++++--------- types/option.go | 10 ++ 5 files changed, 192 insertions(+), 55 deletions(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 8f0319d8..85212a19 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -13,7 +13,7 @@ on: env: GreenfieldTag: v0.2.4-alpha.2 - GreenfieldStorageProviderTag: v0.2.4-alpha.10 + GreenfieldStorageProviderTag: v0.2.4-alpha.13 GOPRIVATE: github.com/bnb-chain GH_ACCESS_TOKEN: ${{ secrets.GH_TOKEN }} MYSQL_USER: root diff --git a/client/api_bucket.go b/client/api_bucket.go index daacd6e0..f50cc653 100644 --- a/client/api_bucket.go +++ b/client/api_bucket.go @@ -6,6 +6,7 @@ import ( "encoding/xml" "errors" "fmt" + govTypes "github.com/cosmos/cosmos-sdk/x/gov/types" "io" "math" "net/http" @@ -64,6 +65,7 @@ type Bucket interface { ListBucketsByBucketID(ctx context.Context, bucketIds []uint64, opts types.EndPointOptions) (types.ListBucketsByBucketIDResponse, error) GetMigrateBucketApproval(ctx context.Context, migrateBucketMsg *storageTypes.MsgMigrateBucket) (*storageTypes.MsgMigrateBucket, error) MigrateBucket(ctx context.Context, bucketName string, opts types.MigrateBucketOptions) (string, error) + CancelMigrateBucket(ctx context.Context, bucketName string, opts types.CancelMigrateBucketOptions) (uint64, string, error) } // GetCreateBucketApproval returns the signature info for the approval of preCreating resources @@ -741,3 +743,21 @@ func (c *client) MigrateBucket(ctx context.Context, bucketName string, opts type return txnHash, nil } + +// CancelMigrateBucket get approval of migrating bucket and send migrateBucket txn to greenfield chain, it returns the transaction hash value and error +func (c *client) CancelMigrateBucket(ctx context.Context, bucketName string, opts types.CancelMigrateBucketOptions) (uint64, string, error) { + govModuleAddress, err := c.GetModuleAccountByName(ctx, govTypes.ModuleName) + if err != nil { + return 0, "", err + } + cancelBucketMsg := storageTypes.NewMsgCancelMigrateBucket( + govModuleAddress.GetAddress(), bucketName, + ) + + err = cancelBucketMsg.ValidateBasic() + if err != nil { + return 0, "", err + } + + return c.SubmitProposal(ctx, []sdk.Msg{cancelBucketMsg}, opts.ProposalDepositAmount, opts.ProposalTitle, opts.ProposalSummary, types.SubmitProposalOptions{Metadata: opts.ProposalMetaData, TxOption: opts.TxOpts}) +} diff --git a/e2e/basesuite/suite.go b/e2e/basesuite/suite.go index d19dbf25..99f3b640 100644 --- a/e2e/basesuite/suite.go +++ b/e2e/basesuite/suite.go @@ -43,9 +43,10 @@ func ParseMnemonicFromFile(fileName string) string { type BaseSuite struct { suite.Suite - DefaultAccount *types.Account - Client client.Client - ClientContext context.Context + DefaultAccount *types.Account + Client client.Client + ClientContext context.Context + ChallengeClient client.Client } // ParseValidatorMnemonic read the validator mnemonic from file @@ -53,6 +54,16 @@ func ParseValidatorMnemonic(i int) string { return ParseMnemonicFromFile(fmt.Sprintf("../../greenfield/deployment/localup/.local/validator%d/info", i)) } +func (s *BaseSuite) NewChallengeClient() { + mnemonic := ParseMnemonicFromFile(fmt.Sprintf("../../greenfield/deployment/localup/.local/challenger%d/challenger_info", 0)) + challengeAcc, err := types.NewAccountFromMnemonic("challenge_account", mnemonic) + s.Require().NoError(err) + s.ChallengeClient, err = client.New(ChainID, Endpoint, client.Option{ + DefaultAccount: challengeAcc, + }) + s.Require().NoError(err) +} + func (s *BaseSuite) SetupSuite() { mnemonic := ParseValidatorMnemonic(0) account, err := types.NewAccountFromMnemonic("test", mnemonic) @@ -63,4 +74,5 @@ func (s *BaseSuite) SetupSuite() { s.Require().NoError(err) s.ClientContext = context.Background() s.DefaultAccount = account + s.NewChallengeClient() } diff --git a/e2e/e2e_migrate_bucket_test.go b/e2e/e2e_migrate_bucket_test.go index 1a20a819..7c2c5f94 100644 --- a/e2e/e2e_migrate_bucket_test.go +++ b/e2e/e2e_migrate_bucket_test.go @@ -2,8 +2,11 @@ package e2e import ( "bytes" + "context" "fmt" + "github.com/stretchr/testify/suite" "io" + "testing" "time" "github.com/bnb-chain/greenfield-go-sdk/e2e/basesuite" @@ -18,22 +21,22 @@ type BucketMigrateTestSuite struct { PrimarySP spTypes.StorageProvider } -//func (s *BucketMigrateTestSuite) SetupSuite() { -// s.BaseSuite.SetupSuite() -// -// spList, err := s.Client.ListStorageProviders(s.ClientContext, false) -// s.Require().NoError(err) -// for _, sp := range spList { -// if sp.Endpoint != "https://sp0.greenfield.io" { -// s.PrimarySP = sp -// break -// } -// } -//} - -//func TestBucketMigrateTestSuiteTestSuite(t *testing.T) { -// suite.Run(t, new(BucketMigrateTestSuite)) -//} +func (s *BucketMigrateTestSuite) SetupSuite() { + s.BaseSuite.SetupSuite() + + spList, err := s.Client.ListStorageProviders(s.ClientContext, false) + s.Require().NoError(err) + for _, sp := range spList { + if sp.Endpoint != "https://sp0.greenfield.io" { + s.PrimarySP = sp + break + } + } +} + +func TestBucketMigrateTestSuiteTestSuite(t *testing.T) { + suite.Run(t, new(BucketMigrateTestSuite)) +} func (s *BucketMigrateTestSuite) CreateObjects(bucketName string, count int) ([]*types.ObjectDetail, []bytes.Buffer, error) { var ( @@ -47,12 +50,12 @@ func (s *BucketMigrateTestSuite) CreateObjects(bucketName string, count int) ([] var buffer bytes.Buffer line := `1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,123456789012` // Create 1MiB content where each line contains 1024 characters. - for i := 0; i < 1024*3; i++ { - buffer.WriteString(fmt.Sprintf("[%05d] %s\n", i, line)) + for n := 0; n < 1024*3; n++ { + buffer.WriteString(fmt.Sprintf("[%05d] %s\n", n, line)) } objectName := storageTestUtil.GenRandomObjectName() s.T().Logf("---> CreateObject and HeadObject, bucketname:%s, objectname:%s <---", bucketName, objectName) - objectTx, err := s.Client.CreateObject(s.ClientContext, bucketName, objectName, bytes.NewReader(buffer.Bytes()), types.CreateObjectOptions{}) + objectTx, err := s.Client.CreateObject(s.ClientContext, bucketName, objectName, bytes.NewReader(buffer.Bytes()), types.CreateObjectOptions{Visibility: storageTypes.VISIBILITY_TYPE_PUBLIC_READ}) s.Require().NoError(err) _, err = s.Client.WaitForTx(s.ClientContext, objectTx) s.Require().NoError(err) @@ -99,12 +102,9 @@ func (s *BucketMigrateTestSuite) CreateObjects(bucketName string, count int) ([] return objectDetails, contentBuffer, nil } -// test only one object's case -func (s *BucketMigrateTestSuite) Test_Bucket_Migrate_Simple_Case() { +func (s *BucketMigrateTestSuite) MustCreateBucket(visibility storageTypes.VisibilityType) (string, *storageTypes.BucketInfo) { bucketName := storageTestUtil.GenRandomBucketName() - - // 1) create bucket and object in srcSP - bucketTx, err := s.Client.CreateBucket(s.ClientContext, bucketName, s.PrimarySP.OperatorAddress, types.CreateBucketOptions{}) + bucketTx, err := s.Client.CreateBucket(s.ClientContext, bucketName, s.PrimarySP.OperatorAddress, types.CreateBucketOptions{Visibility: visibility}) s.Require().NoError(err) _, err = s.Client.WaitForTx(s.ClientContext, bucketTx) @@ -113,17 +113,15 @@ func (s *BucketMigrateTestSuite) Test_Bucket_Migrate_Simple_Case() { bucketInfo, err := s.Client.HeadBucket(s.ClientContext, bucketName) s.Require().NoError(err) if err == nil { - s.Require().Equal(bucketInfo.Visibility, storageTypes.VISIBILITY_TYPE_PRIVATE) + s.Require().Equal(bucketInfo.Visibility, visibility) } - // test only one object's case - objectDetails, contentBuffer, err := s.CreateObjects(bucketName, 1) - s.Require().NoError(err) + s.T().Logf("success to create a new bucket: %s", bucketInfo) - objectDetail := objectDetails[0] - buffer := contentBuffer[0] + return bucketName, bucketInfo +} - // selete a storage provider to miragte +func (s *BucketMigrateTestSuite) SelectDestSP(objectDetail *types.ObjectDetail) *spTypes.StorageProvider { sps, err := s.Client.ListStorageProviders(s.ClientContext, true) s.Require().NoError(err) @@ -144,15 +142,17 @@ func (s *BucketMigrateTestSuite) Test_Bucket_Migrate_Simple_Case() { } s.Require().NotNil(destSP) - s.T().Logf(":Migrate Bucket DstPrimarySPID %d", destSP.GetId()) - - // normal no conflict send migrate bucket transaction - txhash, err := s.Client.MigrateBucket(s.ClientContext, bucketName, types.MigrateBucketOptions{TxOpts: nil, DstPrimarySPID: destSP.GetId(), IsAsyncMode: false}) - s.Require().NoError(err) + return destSP +} - s.T().Logf("MigrateBucket : %s", txhash) +func (s *BucketMigrateTestSuite) waitUntilBucketMigrateFinish(bucketName string, destSP *spTypes.StorageProvider) *storageTypes.BucketInfo { + var ( + bucketInfo *storageTypes.BucketInfo + err error + ) - for { + // wait 5 minutes + for i := 0; i < 100; i++ { bucketInfo, err = s.Client.HeadBucket(s.ClientContext, bucketName) s.T().Logf("HeadBucket: %s", bucketInfo) s.Require().NoError(err) @@ -165,6 +165,35 @@ func (s *BucketMigrateTestSuite) Test_Bucket_Migrate_Simple_Case() { family, err := s.Client.QueryVirtualGroupFamily(s.ClientContext, bucketInfo.GlobalVirtualGroupFamilyId) s.Require().NoError(err) s.Require().Equal(family.PrimarySpId, destSP.GetId()) + + return bucketInfo +} + +// test only one object's case +func (s *BucketMigrateTestSuite) Test_Bucket_Migrate_Simple_Case() { + + // 1) create bucket and object in srcSP + bucketName, _ := s.MustCreateBucket(storageTypes.VISIBILITY_TYPE_PUBLIC_READ) + + // test only one object's case + objectDetails, contentBuffer, err := s.CreateObjects(bucketName, 1) + s.Require().NoError(err) + + objectDetail := objectDetails[0] + buffer := contentBuffer[0] + + // selete a storage provider to miragte + destSP := s.SelectDestSP(objectDetail) + + s.T().Logf(":Migrate Bucket DstPrimarySPID %d", destSP.GetId()) + + // normal no conflict send migrate bucket transaction + txhash, err := s.Client.MigrateBucket(s.ClientContext, bucketName, types.MigrateBucketOptions{TxOpts: nil, DstPrimarySPID: destSP.GetId(), IsAsyncMode: false}) + s.Require().NoError(err) + + s.T().Logf("MigrateBucket : %s", txhash) + s.waitUntilBucketMigrateFinish(bucketName, destSP) + ior, info, err := s.Client.GetObject(s.ClientContext, bucketName, objectDetail.ObjectInfo.ObjectName, types.GetObjectOptions{}) s.Require().NoError(err) if err == nil { @@ -173,24 +202,13 @@ func (s *BucketMigrateTestSuite) Test_Bucket_Migrate_Simple_Case() { s.Require().NoError(err) s.Require().Equal(objectBytes, buffer.Bytes()) } + s.CheckChallenge(uint32(objectDetail.ObjectInfo.Id.Uint64())) } // test only conflict sp's case func (s *BucketMigrateTestSuite) Test_Bucket_Migrate_Simple_Conflict_Case() { - bucketName := storageTestUtil.GenRandomBucketName() - // 1) create bucket and object in srcSP - bucketTx, err := s.Client.CreateBucket(s.ClientContext, bucketName, s.PrimarySP.OperatorAddress, types.CreateBucketOptions{}) - s.Require().NoError(err) - - _, err = s.Client.WaitForTx(s.ClientContext, bucketTx) - s.Require().NoError(err) - - bucketInfo, err := s.Client.HeadBucket(s.ClientContext, bucketName) - s.Require().NoError(err) - if err == nil { - s.Require().Equal(bucketInfo.Visibility, storageTypes.VISIBILITY_TYPE_PRIVATE) - } + bucketName, _ := s.MustCreateBucket(storageTypes.VISIBILITY_TYPE_PUBLIC_READ) // test only one object's case objectDetails, contentBuffer, err := s.CreateObjects(bucketName, 1) @@ -229,6 +247,8 @@ func (s *BucketMigrateTestSuite) Test_Bucket_Migrate_Simple_Conflict_Case() { s.T().Logf("MigrateBucket : %s", txhash) + var bucketInfo *storageTypes.BucketInfo + for { bucketInfo, err = s.Client.HeadBucket(s.ClientContext, bucketName) s.T().Logf("HeadBucket: %s", bucketInfo) @@ -250,4 +270,79 @@ func (s *BucketMigrateTestSuite) Test_Bucket_Migrate_Simple_Conflict_Case() { s.Require().NoError(err) s.Require().Equal(objectBytes, buffer.Bytes()) } + s.CheckChallenge(uint32(objectDetail.ObjectInfo.Id.Uint64())) +} + +// test empty bucket case +func (s *BucketMigrateTestSuite) Test_Empty_Bucket_Migrate_Simple_Case() { + // 1) create bucket and object in srcSP + bucketName, bucketInfo := s.MustCreateBucket(storageTypes.VISIBILITY_TYPE_PUBLIC_READ) + + s.T().Logf("CreateBucket : %s", bucketInfo) + virtualGroupFamily, err := s.Client.QueryVirtualGroupFamily(s.ClientContext, bucketInfo.GetGlobalVirtualGroupFamilyId()) + s.Require().NoError(err) + s.T().Logf("virtualGroupFamily : %s", virtualGroupFamily) + + if err == nil { + s.Require().Equal(bucketInfo.Visibility, storageTypes.VISIBILITY_TYPE_PUBLIC_READ) + } + + time.Sleep(5 * time.Second) + // selete a storage provider to miragte + sps, err := s.Client.ListStorageProviders(s.ClientContext, true) + s.Require().NoError(err) + + var destSP *spTypes.StorageProvider + for _, sp := range sps { + if sp.GetId() != virtualGroupFamily.GetPrimarySpId() { + destSP = &sp + break + } + } + s.Require().NotNil(destSP) + + s.T().Logf(":Migrate Bucket DstPrimarySPID %s", destSP.String()) + + // normal no conflict send migrate bucket transaction + txhash, err := s.Client.MigrateBucket(s.ClientContext, bucketName, types.MigrateBucketOptions{TxOpts: nil, DstPrimarySPID: destSP.GetId(), IsAsyncMode: false}) + s.Require().NoError(err) + + s.T().Logf("MigrateBucket : %s", txhash) + + for { + bucketInfo, err = s.Client.HeadBucket(s.ClientContext, bucketName) + s.T().Logf("HeadBucket: %s", bucketInfo) + s.Require().NoError(err) + if bucketInfo.BucketStatus != storageTypes.BUCKET_STATUS_MIGRATING { + break + } + time.Sleep(3 * time.Second) + } + + family, err := s.Client.QueryVirtualGroupFamily(s.ClientContext, bucketInfo.GlobalVirtualGroupFamilyId) + s.Require().NoError(err) + s.Require().Equal(family.PrimarySpId, destSP.GetId()) +} + +func (s *BucketMigrateTestSuite) CheckChallenge(objectId uint32) bool { + time.Sleep(5 * time.Second) + i := objectId + infos, err := s.Client.HeadObjectByID(context.Background(), fmt.Sprintf("%d", i)) + s.Require().NoError(err) + if infos.ObjectInfo.ObjectStatus == storageTypes.OBJECT_STATUS_SEALED { + reader, _, err := s.Client.GetObject(context.Background(), infos.ObjectInfo.BucketName, infos.ObjectInfo.ObjectName, types.GetObjectOptions{}) + s.NoError(err, fmt.Sprintf("%d", i), infos.ObjectInfo.BucketName, infos.ObjectInfo.ObjectName) + _, err = io.ReadAll(reader) + s.NoError(err, fmt.Sprintf("%d", i), infos.ObjectInfo.BucketName, infos.ObjectInfo.ObjectName) + for j := -1; j < 6; j++ { + s.T().Logf("====challenge %v,%v,=====", i, j) + _, errPk := s.ChallengeClient.GetChallengeInfo(context.Background(), infos.ObjectInfo.Id.String(), 0, j, types.GetChallengeInfoOptions{}) + s.NoError(errPk, infos.ObjectInfo.BucketName, infos.ObjectInfo.ObjectName, i, j) + if errPk != nil { + s.T().Errorf(infos.ObjectInfo.BucketName, infos.ObjectInfo.ObjectName, i, j) + } + } + } + + return true } diff --git a/types/option.go b/types/option.go index 15338006..186ac5c3 100644 --- a/types/option.go +++ b/types/option.go @@ -28,6 +28,16 @@ type MigrateBucketOptions struct { IsAsyncMode bool // indicate whether to create the bucket in asynchronous mode } +type CancelMigrateBucketOptions struct { + ProposalDepositAmount math.Int // wei BNB + + ProposalTitle string + ProposalSummary string + ProposalMetaData string + TxOpts gnfdsdktypes.TxOption + IsAsyncMode bool // indicate whether to create the bucket in asynchronous mode +} + type VoteProposalOptions struct { Metadata string TxOption gnfdsdktypes.TxOption From 1b48a48cce714e4dec8d29e2439148d8b3621f3c Mon Sep 17 00:00:00 2001 From: Chris Li Date: Mon, 28 Aug 2023 08:23:33 +0800 Subject: [PATCH 2/4] fix: wait seal object (#164) --- e2e/basesuite/suite.go | 23 +++++++++++++++++++++++ e2e/e2e_migrate_bucket_test.go | 5 ++++- e2e/e2e_storage_test.go | 25 ++----------------------- 3 files changed, 29 insertions(+), 24 deletions(-) diff --git a/e2e/basesuite/suite.go b/e2e/basesuite/suite.go index 99f3b640..ef60da54 100644 --- a/e2e/basesuite/suite.go +++ b/e2e/basesuite/suite.go @@ -4,8 +4,10 @@ import ( "bufio" "context" "fmt" + storageTypes "github.com/bnb-chain/greenfield/x/storage/types" "os" "path/filepath" + "time" "github.com/bnb-chain/greenfield-go-sdk/client" "github.com/bnb-chain/greenfield-go-sdk/types" @@ -76,3 +78,24 @@ func (s *BaseSuite) SetupSuite() { s.DefaultAccount = account s.NewChallengeClient() } + +func (s *BaseSuite) WaitSealObject(bucketName string, objectName string) { + startCheckTime := time.Now() + var ( + objectDetail *types.ObjectDetail + err error + ) + + // wait 300s + for i := 0; i < 100; i++ { + objectDetail, err = s.Client.HeadObject(s.ClientContext, bucketName, objectName) + s.Require().NoError(err) + if objectDetail.ObjectInfo.GetObjectStatus() == storageTypes.OBJECT_STATUS_SEALED { + break + } + time.Sleep(3 * time.Second) + } + + s.Require().Equal(objectDetail.ObjectInfo.GetObjectStatus().String(), "OBJECT_STATUS_SEALED") + s.T().Logf("---> Wait Seal Object cost %d ms, <---", time.Since(startCheckTime).Milliseconds()) +} diff --git a/e2e/e2e_migrate_bucket_test.go b/e2e/e2e_migrate_bucket_test.go index 7c2c5f94..f44f0499 100644 --- a/e2e/e2e_migrate_bucket_test.go +++ b/e2e/e2e_migrate_bucket_test.go @@ -81,7 +81,10 @@ func (s *BucketMigrateTestSuite) CreateObjects(bucketName string, count int) ([] s.Require().NoError(err) } - time.Sleep(20 * time.Second) + for _, objectName := range objectNames { + s.WaitSealObject(bucketName, objectName) + } + // seal object for idx, objectName := range objectNames { objectDetail, err := s.Client.HeadObject(s.ClientContext, bucketName, objectName) diff --git a/e2e/e2e_storage_test.go b/e2e/e2e_storage_test.go index df6e2620..047b7244 100644 --- a/e2e/e2e_storage_test.go +++ b/e2e/e2e_storage_test.go @@ -174,7 +174,7 @@ func (s *StorageTestSuite) Test_Object() { bytes.NewReader(buffer.Bytes()), types.PutObjectOptions{}) s.Require().NoError(err) - s.waitSealObject(bucketName, objectName) + s.WaitSealObject(bucketName, objectName) ior, info, err := s.Client.GetObject(s.ClientContext, bucketName, objectName, types.GetObjectOptions{}) s.Require().NoError(err) @@ -334,27 +334,6 @@ func DownloadErrorHooker(segment int64) error { return nil } -func (s *StorageTestSuite) waitSealObject(bucketName string, objectName string) { - startCheckTime := time.Now() - var ( - objectDetail *types.ObjectDetail - err error - ) - - // wait 300s - for i := 0; i < 100; i++ { - objectDetail, err = s.Client.HeadObject(s.ClientContext, bucketName, objectName) - s.Require().NoError(err) - if objectDetail.ObjectInfo.GetObjectStatus() == storageTypes.OBJECT_STATUS_SEALED { - break - } - time.Sleep(3 * time.Second) - } - - s.Require().Equal(objectDetail.ObjectInfo.GetObjectStatus().String(), "OBJECT_STATUS_SEALED") - s.T().Logf("---> Wait Seal Object cost %d ms, <---", time.Since(startCheckTime).Milliseconds()) -} - func (s *StorageTestSuite) createBigObjectWithoutPutObject() (bucket string, object string, objectbody bytes.Buffer) { bucketName := storageTestUtil.GenRandomBucketName() objectName := storageTestUtil.GenRandomObjectName() @@ -464,7 +443,7 @@ func (s *StorageTestSuite) Test_Resumable_Upload_And_Download() { bytes.NewReader(buffer.Bytes()), types.PutObjectOptions{PartSize: partSize16MB}) s.Require().NoError(err) - s.waitSealObject(bucketName, objectName) + s.WaitSealObject(bucketName, objectName) // 3) FGetObjectResumable compare with FGetObject fileName := "test-file-" + storageTestUtil.GenRandomObjectName() From 22e688e7b7979c8cee741a626be143c3b11298f0 Mon Sep 17 00:00:00 2001 From: flywukong <2229306838@qq.com> Date: Mon, 28 Aug 2023 13:23:58 +0800 Subject: [PATCH 3/4] feat: support get consumed free quota (#163) * feat: support get consumed free quota * fix: fix e2e err * fix: fix version * fix: fix e2e version * fix: fix test --- .github/workflows/e2e.yml | 2 +- e2e/e2e_storage_test.go | 46 +++++++++++++++++++++++++++++++-------- types/list.go | 3 ++- 3 files changed, 40 insertions(+), 11 deletions(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 85212a19..b557c5a5 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -13,7 +13,7 @@ on: env: GreenfieldTag: v0.2.4-alpha.2 - GreenfieldStorageProviderTag: v0.2.4-alpha.13 + GreenfieldStorageProviderTag: develop GOPRIVATE: github.com/bnb-chain GH_ACCESS_TOKEN: ${{ secrets.GH_TOKEN }} MYSQL_USER: root diff --git a/e2e/e2e_storage_test.go b/e2e/e2e_storage_test.go index 047b7244..70fe9d57 100644 --- a/e2e/e2e_storage_test.go +++ b/e2e/e2e_storage_test.go @@ -7,6 +7,7 @@ import ( "os" "path/filepath" "sort" + "sync" "testing" "time" @@ -169,21 +170,48 @@ func (s *StorageTestSuite) Test_Object() { s.Require().Equal(objectDetail.ObjectInfo.ObjectName, objectName) s.Require().Equal(objectDetail.ObjectInfo.GetObjectStatus().String(), "OBJECT_STATUS_CREATED") - s.T().Logf("---> PutObject and GetObject, objectName:%s objectSize:%d <---", objectName, int64(buffer.Len())) - err = s.Client.PutObject(s.ClientContext, bucketName, objectName, int64(buffer.Len()), + objectSize := int64(buffer.Len()) + s.T().Logf("---> PutObject and GetObject, objectName:%s objectSize:%d <---", objectName, objectSize) + err = s.Client.PutObject(s.ClientContext, bucketName, objectName, objectSize, bytes.NewReader(buffer.Bytes()), types.PutObjectOptions{}) s.Require().NoError(err) s.WaitSealObject(bucketName, objectName) - ior, info, err := s.Client.GetObject(s.ClientContext, bucketName, objectName, types.GetObjectOptions{}) - s.Require().NoError(err) - if err == nil { - s.Require().Equal(info.ObjectName, objectName) - objectBytes, err := io.ReadAll(ior) - s.Require().NoError(err) - s.Require().Equal(objectBytes, buffer.Bytes()) + concurrentNumber := 5 + downloadCount := 5 + quota0, err := s.Client.GetBucketReadQuota(s.ClientContext, bucketName) + s.Require().NoError(err) + + var wg sync.WaitGroup + wg.Add(concurrentNumber) + + for i := 0; i < concurrentNumber; i++ { + go func() { + defer wg.Done() + for j := 0; j < downloadCount; j++ { + objectContent, _, err := s.Client.GetObject(s.ClientContext, bucketName, objectName, types.GetObjectOptions{}) + if err != nil { + fmt.Printf("error: %v", err) + quota2, _ := s.Client.GetBucketReadQuota(s.ClientContext, bucketName) + fmt.Printf("quota: %v", quota2) + } + objectBytes, err := io.ReadAll(objectContent) + s.Require().NoError(err) + s.Require().Equal(objectBytes, buffer.Bytes()) + } + }() } + wg.Wait() + + expectQuotaUsed := int(objectSize) * concurrentNumber * downloadCount + quota1, err := s.Client.GetBucketReadQuota(s.ClientContext, bucketName) + s.Require().NoError(err) + consumedQuota := quota1.ReadConsumedSize - quota0.ReadConsumedSize + freeQuotaConsumed := quota1.FreeConsumedSize - quota0.FreeConsumedSize + // the consumed quota and free quota should be right + s.Require().Equal(uint64(expectQuotaUsed), consumedQuota) + s.Require().Equal(uint64(expectQuotaUsed), freeQuotaConsumed) s.T().Log("---> PutObjectPolicy <---") principal, _, err := types.NewAccount("principal") diff --git a/types/list.go b/types/list.go index ad661126..e6c34c9a 100644 --- a/types/list.go +++ b/types/list.go @@ -14,7 +14,8 @@ type QuotaInfo struct { BucketID string `xml:"BucketID"` ReadQuotaSize uint64 `xml:"ReadQuotaSize"` // the bucket read quota value on chain SPFreeReadQuotaSize uint64 `xml:"SPFreeReadQuotaSize"` // the free quota of this month - ReadConsumedSize uint64 `xml:"ReadConsumedSize"` // the consumed read quota of this month + ReadConsumedSize uint64 `xml:"ReadConsumedSize"` // the consumed total read quota of this month + FreeConsumedSize uint64 `xml:"FreeConsumedSize"` // the consumed free quota } type ReadRecord struct { From 8f4087fe6f250f1e014868ab51b75ac2e85e6d72 Mon Sep 17 00:00:00 2001 From: Alexxxxxx <118710506+alexgao001@users.noreply.github.com> Date: Tue, 29 Aug 2023 15:49:02 +0800 Subject: [PATCH 4/4] chore: Prepare v0.2.4 (#167) * prepare for v0.2.4 * update readme --- .github/workflows/e2e.yml | 2 +- README.md | 11 ++++++----- go.mod | 14 +++++++------- go.sum | 30 +++++++++++++++--------------- 4 files changed, 29 insertions(+), 28 deletions(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index b557c5a5..b37dcb8e 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -12,7 +12,7 @@ on: - develop env: - GreenfieldTag: v0.2.4-alpha.2 + GreenfieldTag: v0.2.4 GreenfieldStorageProviderTag: develop GOPRIVATE: github.com/bnb-chain GH_ACCESS_TOKEN: ${{ secrets.GH_TOKEN }} diff --git a/README.md b/README.md index 20824929..419112a7 100644 --- a/README.md +++ b/README.md @@ -37,13 +37,14 @@ $ go get github.com/bnb-chain/greenfield-go-sdk replace dependencies ```go.mod -cosmossdk.io/api => github.com/bnb-chain/greenfield-cosmos-sdk/api v0.0.0-20230425074444-eb5869b05fe9 -cosmossdk.io/math => github.com/bnb-chain/greenfield-cosmos-sdk/math v0.0.0-20230425074444-eb5869b05fe9 -github.com/cometbft/cometbft => github.com/bnb-chain/greenfield-cometbft v0.0.2 +cosmossdk.io/api => github.com/bnb-chain/greenfield-cosmos-sdk/api v0.0.0-20230816082903-b48770f5e210 +cosmossdk.io/math => github.com/bnb-chain/greenfield-cosmos-sdk/math v0.0.0-20230816082903-b48770f5e210 +github.com/cometbft/cometbft => github.com/bnb-chain/greenfield-cometbft v0.0.3 github.com/cometbft/cometbft-db => github.com/bnb-chain/greenfield-cometbft-db v0.8.1-alpha.1 -github.com/cosmos/cosmos-sdk => github.com/bnb-chain/greenfield-cosmos-sdk v0.2.3 -github.com/cosmos/iavl => github.com/bnb-chain/greenfield-iavl v0.20.1-alpha.1 +github.com/cosmos/cosmos-sdk => github.com/bnb-chain/greenfield-cosmos-sdk v0.2.4 +github.com/cosmos/iavl => github.com/bnb-chain/greenfield-iavl v0.20.1 github.com/syndtr/goleveldb => github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 +github.com/consensys/gnark-crypto => github.com/consensys/gnark-crypto v0.7.0 ``` ### Initialize Client diff --git a/go.mod b/go.mod index 335b0611..5e5e417a 100644 --- a/go.mod +++ b/go.mod @@ -5,12 +5,12 @@ go 1.20 require ( cosmossdk.io/errors v1.0.0-beta.7 cosmossdk.io/math v1.0.1 - github.com/bnb-chain/greenfield v0.2.4-alpha.2 + github.com/bnb-chain/greenfield v0.2.4 github.com/bnb-chain/greenfield-common/go v0.0.0-20230809025353-fd0519705054 github.com/cometbft/cometbft v0.37.2 github.com/consensys/gnark-crypto v0.7.0 github.com/cosmos/cosmos-sdk v0.47.3 - github.com/ethereum/go-ethereum v1.10.22 + github.com/ethereum/go-ethereum v1.10.26 github.com/prysmaticlabs/prysm v0.0.0-20220124113610-e26cde5e091b github.com/rs/zerolog v1.29.1 github.com/stretchr/testify v1.8.4 @@ -86,14 +86,14 @@ require ( github.com/linxGnu/grocksdb v1.7.16 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.18 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0 // indirect github.com/minio/sha256-simd v1.0.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/mtibben/percent v0.2.1 // indirect - github.com/pelletier/go-toml/v2 v2.0.7 // indirect + github.com/pelletier/go-toml/v2 v2.0.8 // indirect github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect @@ -113,7 +113,7 @@ require ( github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/viper v1.15.0 // indirect github.com/subosito/gotenv v1.4.2 // indirect - github.com/supranational/blst v0.3.8-0.20220526154634-513d2456b344 // indirect + github.com/supranational/blst v0.3.11 // indirect github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect github.com/tendermint/go-amino v0.16.0 // indirect github.com/thomaso-mirodin/intmath v0.0.0-20160323211736-5dc6d854e46e // indirect @@ -144,10 +144,10 @@ replace ( cosmossdk.io/api => github.com/bnb-chain/greenfield-cosmos-sdk/api v0.0.0-20230816082903-b48770f5e210 cosmossdk.io/math => github.com/bnb-chain/greenfield-cosmos-sdk/math v0.0.0-20230816082903-b48770f5e210 github.com/btcsuite/btcd => github.com/btcsuite/btcd v0.23.0 - github.com/cometbft/cometbft => github.com/bnb-chain/greenfield-cometbft v0.0.3-alpha.1 + github.com/cometbft/cometbft => github.com/bnb-chain/greenfield-cometbft v0.0.3 github.com/cometbft/cometbft-db => github.com/bnb-chain/greenfield-cometbft-db v0.8.1-alpha.1 github.com/confio/ics23/go => github.com/cosmos/cosmos-sdk/ics23/go v0.8.0 - github.com/cosmos/cosmos-sdk => github.com/bnb-chain/greenfield-cosmos-sdk v0.2.4-alpha.2 + github.com/cosmos/cosmos-sdk => github.com/bnb-chain/greenfield-cosmos-sdk v0.2.4 github.com/cosmos/iavl => github.com/bnb-chain/greenfield-iavl v0.20.1 github.com/syndtr/goleveldb => github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 ) diff --git a/go.sum b/go.sum index c38b756e..b5fd3ce2 100644 --- a/go.sum +++ b/go.sum @@ -152,16 +152,16 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816 h1:41iFGWnSlI2gVpmOtVTJZNodLdLQLn/KsJqFvXwnd/s= github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c= -github.com/bnb-chain/greenfield v0.2.4-alpha.2 h1:l/GkTKN3M6TnxC3Ak9Tm8Yd/6qW9bxSMc+/pUGvLgTU= -github.com/bnb-chain/greenfield v0.2.4-alpha.2/go.mod h1:rqUmxfdTvIwqa56H9QASdKQ7zrytbEY3zLMQAl2FMiU= -github.com/bnb-chain/greenfield-cometbft v0.0.3-alpha.1 h1:nCLXxYdkDIh5bQMxtb14TBwiut/xq2e0DqPVTLy9vtI= -github.com/bnb-chain/greenfield-cometbft v0.0.3-alpha.1/go.mod h1:3nGT4Z9fHwgRlBY/rofn0rSarnIcNbuhz/eq0XlLlkg= +github.com/bnb-chain/greenfield v0.2.4 h1:3knYY3KbEYoysnTAp3+oh2YyeWG2Au0kXYSdZHyzV+k= +github.com/bnb-chain/greenfield v0.2.4/go.mod h1:7FzduaDVOXpbiWMo0JoH8odXgwEfGJ3ug20BIgPDVKE= +github.com/bnb-chain/greenfield-cometbft v0.0.3 h1:tv8NMy3bzX/1urqXGQIIAZSLy83loiI+dG0VKeyh1CY= +github.com/bnb-chain/greenfield-cometbft v0.0.3/go.mod h1:f35mk/r5ab6yvzlqEWZt68LfUje68sYgMpVlt2CUYMk= github.com/bnb-chain/greenfield-cometbft-db v0.8.1-alpha.1 h1:XcWulGacHVRiSCx90Q8Y//ajOrLNBQWR/KDB89dy3cU= github.com/bnb-chain/greenfield-cometbft-db v0.8.1-alpha.1/go.mod h1:ey1CiK4bYo1RBNJLRiVbYr5CMdSxci9S/AZRINLtppI= github.com/bnb-chain/greenfield-common/go v0.0.0-20230809025353-fd0519705054 h1:74pdUdHjo9QNgjSifIgzbDcloqFJ2I+qo715tOXy/oM= github.com/bnb-chain/greenfield-common/go v0.0.0-20230809025353-fd0519705054/go.mod h1:GEjCahULmz99qx5k8WGWa7cTXIUjoNMNW+J92I+kTWg= -github.com/bnb-chain/greenfield-cosmos-sdk v0.2.4-alpha.2 h1:mCojTDXd//s34SiHqRolG7saZSG9YHQ9WzPFF8rL4Zo= -github.com/bnb-chain/greenfield-cosmos-sdk v0.2.4-alpha.2/go.mod h1:2jk2ijERIAv8wxQ/IJSmzQKazCnR6YGvICk4O1YrT9M= +github.com/bnb-chain/greenfield-cosmos-sdk v0.2.4 h1:09ST+MTEAyjyBSc4ZjZzHxpNLMnIIkZ518jJVRtrKFc= +github.com/bnb-chain/greenfield-cosmos-sdk v0.2.4/go.mod h1:y3hDhQhil5hMIhwBTpu07RZBF30ZITkoE+GHhVZChtY= github.com/bnb-chain/greenfield-cosmos-sdk/api v0.0.0-20230816082903-b48770f5e210 h1:GHPbV2bC+gmuO6/sG0Tm8oGal3KKSRlyE+zPscDjlA8= github.com/bnb-chain/greenfield-cosmos-sdk/api v0.0.0-20230816082903-b48770f5e210/go.mod h1:vhsZxXE9tYJeYB5JR4hPhd6Pc/uPf7j1T8IJ7p9FdeM= github.com/bnb-chain/greenfield-cosmos-sdk/math v0.0.0-20230816082903-b48770f5e210 h1:FLVOn4+OVbsKi2+YJX5kmD27/4dRu4FW7xCXFhzDO5s= @@ -321,8 +321,8 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/ethereum/go-ethereum v1.10.13/go.mod h1:W3yfrFyL9C1pHcwY5hmRHVDaorTiQxhYBkKyu5mEDHw= -github.com/ethereum/go-ethereum v1.10.22 h1:HbEgsDo1YTGIf4KB/NNpn+XH+PiNJXUZ9ksRxiqWyMc= -github.com/ethereum/go-ethereum v1.10.22/go.mod h1:EYFyF19u3ezGLD4RqOkLq+ZCXzYbLoNDdZlMt7kyKFg= +github.com/ethereum/go-ethereum v1.10.26 h1:i/7d9RBBwiXCEuyduBQzJw/mKmnvzsN14jqBmytw72s= +github.com/ethereum/go-ethereum v1.10.26/go.mod h1:EYFyF19u3ezGLD4RqOkLq+ZCXzYbLoNDdZlMt7kyKFg= github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= @@ -890,8 +890,8 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= -github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= @@ -1056,8 +1056,8 @@ github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtP github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.0.1-0.20170904195809-1d6b12b7cb29/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml/v2 v2.0.7 h1:muncTPStnKRos5dpVKULv2FVd4bMOhNePj9CjgDb8Us= -github.com/pelletier/go-toml/v2 v2.0.7/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= +github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= +github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/peterh/liner v1.0.1-0.20180619022028-8c1271fcf47f/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= @@ -1262,14 +1262,14 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/supranational/blst v0.3.5/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= -github.com/supranational/blst v0.3.8-0.20220526154634-513d2456b344 h1:m+8fKfQwCAy1QjzINvKe/pYtLjo2dl59x2w9YSEJxuY= -github.com/supranational/blst v0.3.8-0.20220526154634-513d2456b344/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= +github.com/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbeoEm4= +github.com/supranational/blst v0.3.11/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=