From 5c704b2ec25213075cd8f4a136577bdc79a28a00 Mon Sep 17 00:00:00 2001 From: Mykhailo Prakhov Date: Fri, 5 Jan 2024 18:09:38 +0100 Subject: [PATCH] chore: check and manualy verify community if during the fetchCommunity, community was added to the verification loop (#4533) --- protocol/communities/manager.go | 123 ++++++++++-------- protocol/communities/persistence.go | 24 ++++ protocol/communities/persistence_test.go | 8 ++ .../messenger_store_node_request_manager.go | 33 ++++- protocol/messenger_storenode_request_test.go | 52 ++++++++ 5 files changed, 183 insertions(+), 57 deletions(-) diff --git a/protocol/communities/manager.go b/protocol/communities/manager.go index dbe7702bd..217732540 100644 --- a/protocol/communities/manager.go +++ b/protocol/communities/manager.go @@ -435,65 +435,82 @@ func (m *Manager) runOwnerVerificationLoop() { for id, communities := range communitiesToValidate { m.logger.Info("validating communities", zap.String("id", id), zap.Int("count", len(communities))) - for _, communityToValidate := range communities { - signer, description, err := UnwrapCommunityDescriptionMessage(communityToValidate.payload) - if err != nil { - m.logger.Error("failed to unwrap community", zap.Error(err)) - continue - } - - chainID := CommunityDescriptionTokenOwnerChainID(description) - if chainID == 0 { - // This should not happen - m.logger.Error("chain id is 0, ignoring") - continue - } - - m.logger.Info("validating community", zap.String("id", types.EncodeHex(communityToValidate.id)), zap.String("signer", common.PubkeyToHex(signer))) - - ctx, cancel := context.WithTimeout(context.Background(), time.Second*3) - defer cancel() - - owner, err := m.ownerVerifier.SafeGetSignerPubKey(ctx, chainID, id) - if err != nil { - m.logger.Error("failed to get owner", zap.Error(err)) - continue - } - - ownerPK, err := common.HexToPubkey(owner) - if err != nil { - m.logger.Error("failed to convert pk string to ecdsa", zap.Error(err)) - continue - } - - // TODO: handle shards - response, err := m.HandleCommunityDescriptionMessage(signer, description, communityToValidate.payload, ownerPK, nil) - if err != nil { - m.logger.Error("failed to handle community", zap.Error(err)) - err = m.persistence.DeleteCommunityToValidate(communityToValidate.id, communityToValidate.clock) - if err != nil { - m.logger.Error("failed to delete community to validate", zap.Error(err)) - } - continue - } - - if response != nil { - - m.logger.Info("community validated", zap.String("id", types.EncodeHex(communityToValidate.id)), zap.String("signer", common.PubkeyToHex(signer))) - m.publish(&Subscription{TokenCommunityValidated: response}) - err := m.persistence.DeleteCommunitiesToValidateByCommunityID(communityToValidate.id) - if err != nil { - m.logger.Error("failed to delete communities to validate", zap.Error(err)) - } - break - } - } + _, _ = m.validateCommunity(communities) } } } }() } +func (m *Manager) ValidateCommunityByID(communityID types.HexBytes) (*CommunityResponse, error) { + communityToValidate, err := m.persistence.getCommunityToValidateByID(communityID) + if err != nil { + m.logger.Error("failed to validate community by ID", zap.String("id", communityID.String()), zap.Error(err)) + return nil, err + } + + return m.validateCommunity(communityToValidate) + +} + +func (m *Manager) validateCommunity(communityToValidateData []communityToValidate) (*CommunityResponse, error) { + for _, communityToValidate := range communityToValidateData { + signer, description, err := UnwrapCommunityDescriptionMessage(communityToValidate.payload) + if err != nil { + m.logger.Error("failed to unwrap community", zap.Error(err)) + continue + } + + chainID := CommunityDescriptionTokenOwnerChainID(description) + if chainID == 0 { + // This should not happen + m.logger.Error("chain id is 0, ignoring") + continue + } + + m.logger.Info("validating community", zap.String("id", types.EncodeHex(communityToValidate.id)), zap.String("signer", common.PubkeyToHex(signer))) + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*3) + defer cancel() + + owner, err := m.ownerVerifier.SafeGetSignerPubKey(ctx, chainID, types.EncodeHex(communityToValidate.id)) + if err != nil { + m.logger.Error("failed to get owner", zap.Error(err)) + continue + } + + ownerPK, err := common.HexToPubkey(owner) + if err != nil { + m.logger.Error("failed to convert pk string to ecdsa", zap.Error(err)) + continue + } + + // TODO: handle shards + response, err := m.HandleCommunityDescriptionMessage(signer, description, communityToValidate.payload, ownerPK, nil) + if err != nil { + m.logger.Error("failed to handle community", zap.Error(err)) + err = m.persistence.DeleteCommunityToValidate(communityToValidate.id, communityToValidate.clock) + if err != nil { + m.logger.Error("failed to delete community to validate", zap.Error(err)) + } + continue + } + + if response != nil { + + m.logger.Info("community validated", zap.String("id", types.EncodeHex(communityToValidate.id)), zap.String("signer", common.PubkeyToHex(signer))) + m.publish(&Subscription{TokenCommunityValidated: response}) + err := m.persistence.DeleteCommunitiesToValidateByCommunityID(communityToValidate.id) + if err != nil { + m.logger.Error("failed to delete communities to validate", zap.Error(err)) + } + return response, nil + } + } + + return nil, nil +} + func (m *Manager) Stop() error { m.stopped = true close(m.quit) diff --git a/protocol/communities/persistence.go b/protocol/communities/persistence.go index 7bac7e3df..b551034d7 100644 --- a/protocol/communities/persistence.go +++ b/protocol/communities/persistence.go @@ -1489,6 +1489,30 @@ func (p *Persistence) getCommunitiesToValidate() (map[string][]communityToValida } +func (p *Persistence) getCommunityToValidateByID(communityID types.HexBytes) ([]communityToValidate, error) { + communityToValidateArray := []communityToValidate{} + rows, err := p.db.Query(`SELECT id, clock, payload, signer FROM communities_validate_signer WHERE id = ? AND validate_at <= ? ORDER BY clock DESC`, communityID, time.Now().UnixNano()) + + if err != nil { + return nil, err + } + + defer rows.Close() + + for rows.Next() { + communityToValidate := communityToValidate{} + err := rows.Scan(&communityToValidate.id, &communityToValidate.clock, &communityToValidate.payload, &communityToValidate.signer) + if err != nil { + return nil, err + } + + communityToValidateArray = append(communityToValidateArray, communityToValidate) + } + + return communityToValidateArray, nil + +} + func (p *Persistence) DeleteCommunitiesToValidateByCommunityID(communityID []byte) error { _, err := p.db.Exec(`DELETE FROM communities_validate_signer WHERE id = ?`, communityID) return err diff --git a/protocol/communities/persistence_test.go b/protocol/communities/persistence_test.go index 6fc1bf9e2..b0de8b0e1 100644 --- a/protocol/communities/persistence_test.go +++ b/protocol/communities/persistence_test.go @@ -913,3 +913,11 @@ func (s *PersistenceSuite) TestSaveShardInfo() { s.Require().Error(err, sql.ErrNoRows) s.Require().Nil(resultShard) } + +func (s *PersistenceSuite) TestGetCommunityToValidateByID() { + communityID := types.HexBytes{1, 2, 3, 4, 5, 6, 7, 8} + + result, err := s.db.getCommunityToValidateByID(communityID) + s.Require().NoError(err) + s.Require().Len(result, 0) +} diff --git a/protocol/messenger_store_node_request_manager.go b/protocol/messenger_store_node_request_manager.go index 84d1dc746..6702e13e4 100644 --- a/protocol/messenger_store_node_request_manager.go +++ b/protocol/messenger_store_node_request_manager.go @@ -373,15 +373,40 @@ func (r *storeNodeRequest) shouldFetchNextPage(envelopesCount int) (bool, uint32 // Try to get community from database switch r.requestID.RequestType { case storeNodeCommunityRequest: - community, err := r.manager.messenger.communitiesManager.GetByIDString(r.requestID.DataID) - + communityID, err := types.DecodeHex(r.requestID.DataID) if err != nil { - logger.Error("failed to read from database", + logger.Error("failed to decode community ID", zap.String("communityID", r.requestID.DataID), zap.Error(err)) r.result = storeNodeRequestResult{ community: nil, - err: fmt.Errorf("failed to read from database: %w", err), + err: fmt.Errorf("failed to decode community ID: %w", err), + } + return false, 0 // failed to decode community ID, no sense to continue the procedure + } + + // check if community is waiting for a verification and do a verification manually + _, err = r.manager.messenger.communitiesManager.ValidateCommunityByID(communityID) + if err != nil { + logger.Error("failed to validate community by ID", + zap.String("communityID", r.requestID.DataID), + zap.Error(err)) + r.result = storeNodeRequestResult{ + community: nil, + err: fmt.Errorf("failed to validate community by ID: %w", err), + } + return false, 0 // failed to validate community, no sense to continue the procedure + } + + community, err := r.manager.messenger.communitiesManager.GetByID(communityID) + + if err != nil { + logger.Error("failed to read community from database", + zap.String("communityID", r.requestID.DataID), + zap.Error(err)) + r.result = storeNodeRequestResult{ + community: nil, + err: fmt.Errorf("failed to read community from database: %w", err), } return false, 0 // failed to read from database, no sense to continue the procedure } diff --git a/protocol/messenger_storenode_request_test.go b/protocol/messenger_storenode_request_test.go index d652ff44f..f4e4b6d5f 100644 --- a/protocol/messenger_storenode_request_test.go +++ b/protocol/messenger_storenode_request_test.go @@ -8,6 +8,7 @@ import ( "time" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/status-im/status-go/protocol/communities/token" "github.com/status-im/status-go/protocol/transport" "github.com/status-im/status-go/multiaccounts/accounts" @@ -29,7 +30,9 @@ import ( "github.com/status-im/status-go/protocol/requests" "github.com/status-im/status-go/t/helpers" + "github.com/status-im/status-go/services/communitytokens" mailserversDB "github.com/status-im/status-go/services/mailservers" + "github.com/status-im/status-go/services/wallet/bigint" waku2 "github.com/status-im/status-go/wakuv2" wakuV2common "github.com/status-im/status-go/wakuv2/common" ) @@ -59,6 +62,8 @@ type MessengerStoreNodeRequestSuite struct { ownerWaku types.Waku bobWaku types.Waku + collectiblesServiceMock *CollectiblesServiceMock + logger *zap.Logger } @@ -121,6 +126,8 @@ func (s *MessengerStoreNodeRequestSuite) SetupTest() { s.storeNodeAddress = storeNodeListenAddresses[0] s.logger.Info("store node ready", zap.String("address", s.storeNodeAddress)) + + s.collectiblesServiceMock = &CollectiblesServiceMock{} } func (s *MessengerStoreNodeRequestSuite) TearDown() { @@ -173,6 +180,7 @@ func (s *MessengerStoreNodeRequestSuite) newMessenger(shh types.Waku, logger *za WithClusterConfig(params.ClusterConfig{ Fleet: localFleet, }), + WithCommunityTokensService(s.collectiblesServiceMock), } messenger, err := newMessengerWithKey(shh, privateKey, logger, options) @@ -217,6 +225,8 @@ func (s *MessengerStoreNodeRequestSuite) requireCommunitiesEqual(c *communities. s.Require().Equal(expected.Color(), c.Color()) s.Require().Equal(expected.Tags(), c.Tags()) s.Require().Equal(expected.Shard(), c.Shard()) + s.Require().Equal(expected.TokenPermissions(), c.TokenPermissions()) + s.Require().Equal(expected.CommunityTokensMetadata(), c.CommunityTokensMetadata()) } func (s *MessengerStoreNodeRequestSuite) requireContactsEqual(c *Contact, expected *Contact) { @@ -818,3 +828,45 @@ func (s *MessengerStoreNodeRequestSuite) TestFetchRealCommunity() { fmt.Printf("%s --- %s\n", storeNodeName, result.toString()) } } + +func (s *MessengerStoreNodeRequestSuite) TestFetchingCommunityWithOwnerToken() { + s.createOwner() + s.createBob() + + s.waitForAvailableStoreNode(s.owner) + community := s.createCommunity(s.owner) + + // owner mints owner token + var chainID uint64 = 1 + tokenAddress := "token-address" + tokenName := "tokenName" + tokenSymbol := "TSM" + _, err := s.owner.SaveCommunityToken(&token.CommunityToken{ + TokenType: protobuf.CommunityTokenType_ERC721, + CommunityID: community.IDString(), + Address: tokenAddress, + ChainID: int(chainID), + Name: tokenName, + Supply: &bigint.BigInt{}, + Symbol: tokenSymbol, + PrivilegesLevel: token.OwnerLevel, + }, nil) + s.Require().NoError(err) + + // owner adds minted owner token to community + err = s.owner.AddCommunityToken(community.IDString(), int(chainID), tokenAddress) + s.Require().NoError(err) + + // update mock - the signer for the community returned by the contracts should be owner + s.collectiblesServiceMock.SetSignerPubkeyForCommunity(community.ID(), common.PubkeyToHex(&s.owner.identity.PublicKey)) + s.collectiblesServiceMock.SetMockCollectibleContractData(chainID, tokenAddress, + &communitytokens.CollectibleContractData{TotalSupply: &bigint.BigInt{}}) + + community, err = s.owner.communitiesManager.GetByID(community.ID()) + s.Require().NoError(err) + s.Require().Len(community.TokenPermissions(), 1) + + s.waitForAvailableStoreNode(s.bob) + + s.fetchCommunity(s.bob, community.CommunityShard(), community) +}