fix: force verifiedOwner in `handleSyncInstallationCommunity` (#4405) (#4538)

This commit is contained in:
Igor Sirotin 2024-01-08 15:57:57 +00:00 committed by GitHub
parent 53ac61bb8b
commit 405d468e0e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 189 additions and 28 deletions

View File

@ -1372,6 +1372,10 @@ func (o *Community) Description() *protobuf.CommunityDescription {
return o.config.CommunityDescription return o.config.CommunityDescription
} }
func (o *Community) DescriptionProtocolMessage() []byte {
return o.config.CommunityDescriptionProtocolMessage
}
func (o *Community) marshaledDescription() ([]byte, error) { func (o *Community) marshaledDescription() ([]byte, error) {
clone := proto.Clone(o.config.CommunityDescription).(*protobuf.CommunityDescription) clone := proto.Clone(o.config.CommunityDescription).(*protobuf.CommunityDescription)

View File

@ -1638,7 +1638,12 @@ func (m *Manager) HandleCommunityDescriptionMessage(signer *ecdsa.PublicKey, des
if hasTokenOwnership && verifiedOwner != nil { if hasTokenOwnership && verifiedOwner != nil {
// Override verified owner // Override verified owner
m.logger.Info("updating verified owner", zap.String("communityID", community.IDString()), zap.String("owner", common.PubkeyToHex(verifiedOwner))) m.logger.Info("updating verified owner",
zap.String("communityID", community.IDString()),
zap.String("verifiedOwner", common.PubkeyToHex(verifiedOwner)),
zap.String("signer", common.PubkeyToHex(signer)),
zap.String("controlNode", common.PubkeyToHex(community.ControlNode())),
)
// If we are not the verified owner anymore, drop the private key // If we are not the verified owner anymore, drop the private key
if !common.IsPubKeyEqual(verifiedOwner, &m.identity.PublicKey) { if !common.IsPubKeyEqual(verifiedOwner, &m.identity.PublicKey) {

View File

@ -331,7 +331,8 @@ func advertiseCommunityTo(s *suite.Suite, community *communities.Community, owne
messageState := user.buildMessageState() messageState := user.buildMessageState()
messageState.CurrentMessageState = &CurrentMessageState{} messageState.CurrentMessageState = &CurrentMessageState{}
messageState.CurrentMessageState.PublicKey = &user.identity.PublicKey messageState.CurrentMessageState.PublicKey = &user.identity.PublicKey
err = user.handleCommunityDescription(messageState, signer, description, wrappedCommunity, nil) // TODO: handle shards?
err = user.handleCommunityDescription(messageState, signer, description, wrappedCommunity, nil, nil)
s.Require().NoError(err) s.Require().NoError(err)
} }

View File

@ -1,16 +1,20 @@
package protocol package protocol
import ( import (
"bytes"
"context" "context"
"testing" "testing"
"time" "time"
"github.com/golang/protobuf/proto"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"go.uber.org/zap" "go.uber.org/zap"
gethcommon "github.com/ethereum/go-ethereum/common" gethcommon "github.com/ethereum/go-ethereum/common"
hexutil "github.com/ethereum/go-ethereum/common/hexutil" hexutil "github.com/ethereum/go-ethereum/common/hexutil"
utils "github.com/status-im/status-go/common"
gethbridge "github.com/status-im/status-go/eth-node/bridge/geth" gethbridge "github.com/status-im/status-go/eth-node/bridge/geth"
"github.com/status-im/status-go/eth-node/crypto" "github.com/status-im/status-go/eth-node/crypto"
"github.com/status-im/status-go/eth-node/types" "github.com/status-im/status-go/eth-node/types"
@ -581,3 +585,159 @@ func (s *MessengerCommunitiesSignersSuite) TestNewOwnerAcceptRequestToJoin() {
s.joinCommunity(s.alice, community, s.bob) s.joinCommunity(s.alice, community, s.bob)
} }
func (s *MessengerCommunitiesSignersSuite) testDescriptionSignature(description []byte) {
var amm protobuf.ApplicationMetadataMessage
err := proto.Unmarshal(description, &amm)
s.Require().NoError(err)
signer, err := utils.RecoverKey(&amm)
s.Require().NoError(err)
s.NotNil(signer)
}
func (s *MessengerCommunitiesSignersSuite) forceCommunityChange(community *communities.Community, owner *Messenger, user *Messenger) {
newDescription := community.DescriptionText() + " new"
_, err := owner.EditCommunity(&requests.EditCommunity{
CommunityID: community.ID(),
CreateCommunity: requests.CreateCommunity{
Membership: protobuf.CommunityPermissions_AUTO_ACCEPT,
Name: community.Name(),
Color: community.Color(),
Description: newDescription,
},
})
s.Require().NoError(err)
// alice receives new description
_, err = WaitOnMessengerResponse(user, func(r *MessengerResponse) bool {
return len(r.Communities()) > 0 && r.Communities()[0].DescriptionText() == newDescription
}, "new description not received")
s.Require().NoError(err)
}
func (s *MessengerCommunitiesSignersSuite) testSyncCommunity(mintOwnerToken bool) {
community := s.createCommunity(s.john)
s.advertiseCommunityTo(s.john, community, s.alice)
s.joinCommunity(s.john, community, s.alice)
// FIXME: Remove this workaround when fixed:
// https://github.com/status-im/status-go/issues/4413
s.forceCommunityChange(community, s.john, s.alice)
aliceCommunity, err := s.alice.GetCommunityByID(community.ID())
s.Require().NoError(err)
s.testDescriptionSignature(aliceCommunity.DescriptionProtocolMessage())
if mintOwnerToken {
// john mints owner token
var chainID uint64 = 1
tokenAddress := "token-address"
tokenName := "tokenName"
tokenSymbol := "TSM"
_, err := s.john.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)
// john adds minted owner token to community
err = s.john.AddCommunityToken(community.IDString(), int(chainID), tokenAddress)
s.Require().NoError(err)
// update mock - the signer for the community returned by the contracts should be john
s.collectiblesServiceMock.SetSignerPubkeyForCommunity(community.ID(), common.PubkeyToHex(&s.john.identity.PublicKey))
s.collectiblesServiceMock.SetMockCollectibleContractData(chainID, tokenAddress,
&communitytokens.CollectibleContractData{TotalSupply: &bigint.BigInt{}})
// alice accepts community update
_, err = WaitOnSignaledMessengerResponse(
s.alice,
func(r *MessengerResponse) bool {
return len(r.Communities()) > 0 && len(r.Communities()[0].TokenPermissions()) == 1
},
"no communities",
)
s.Require().NoError(err)
}
// Create alice second instance
alice2, err := newMessengerWithKey(
s.shh,
s.alice.identity,
s.logger.With(zap.String("name", "alice-2")),
nil)
s.Require().NoError(err)
_, err = alice2.Start()
s.Require().NoError(err)
defer alice2.Shutdown() // nolint: errcheck
// Create communities backup
clock, _ := s.alice.getLastClockWithRelatedChat()
communitiesBackup, err := s.alice.backupCommunities(context.Background(), clock)
s.Require().NoError(err)
// Find wanted communities in the backup
var syncCommunityMessages []*protobuf.SyncInstallationCommunity
for _, b := range communitiesBackup {
for _, c := range b.Communities {
if bytes.Equal(c.Id, community.ID()) {
syncCommunityMessages = append(syncCommunityMessages, c)
}
}
}
s.Require().Len(syncCommunityMessages, 1)
s.testDescriptionSignature(syncCommunityMessages[0].Description)
// Push the backup into second instance
messageState := alice2.buildMessageState()
err = alice2.HandleSyncInstallationCommunity(messageState, syncCommunityMessages[0], nil)
s.Require().NoError(err)
s.Require().Len(messageState.Response.Communities(), 1)
expectedControlNode := community.PublicKey()
if mintOwnerToken {
expectedControlNode = &s.john.identity.PublicKey
}
responseCommunity := messageState.Response.Communities()[0]
s.Require().Equal(community.IDString(), responseCommunity.IDString())
s.Require().True(common.IsPubKeyEqual(expectedControlNode, responseCommunity.ControlNode()))
}
func (s *MessengerCommunitiesSignersSuite) TestSyncTokenGatedCommunity() {
testCases := []struct {
name string
mintOwnerToken bool
}{
{
name: "general community sync",
mintOwnerToken: false,
},
{
name: "community with token ownership",
mintOwnerToken: true,
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
s.testSyncCommunity(tc.mintOwnerToken)
})
}
}

View File

@ -20,6 +20,8 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
utils "github.com/status-im/status-go/common"
"github.com/status-im/status-go/account" "github.com/status-im/status-go/account"
"github.com/status-im/status-go/eth-node/crypto" "github.com/status-im/status-go/eth-node/crypto"
"github.com/status-im/status-go/eth-node/types" "github.com/status-im/status-go/eth-node/types"
@ -2603,8 +2605,8 @@ func (m *Messenger) passStoredCommunityInfoToSignalHandler(community *communitie
} }
// handleCommunityDescription handles an community description // handleCommunityDescription handles an community description
func (m *Messenger) handleCommunityDescription(state *ReceivedMessageState, signer *ecdsa.PublicKey, description *protobuf.CommunityDescription, rawPayload []byte, shard *protobuf.Shard) error { func (m *Messenger) handleCommunityDescription(state *ReceivedMessageState, signer *ecdsa.PublicKey, description *protobuf.CommunityDescription, rawPayload []byte, verifiedOwner *ecdsa.PublicKey, shard *protobuf.Shard) error {
communityResponse, err := m.communitiesManager.HandleCommunityDescriptionMessage(signer, description, rawPayload, nil, shard) communityResponse, err := m.communitiesManager.HandleCommunityDescriptionMessage(signer, description, rawPayload, verifiedOwner, shard)
if err != nil { if err != nil {
return err return err
} }
@ -2945,7 +2947,7 @@ func (m *Messenger) HandleSyncInstallationCommunity(messageState *ReceivedMessag
} }
func (m *Messenger) handleSyncInstallationCommunity(messageState *ReceivedMessageState, syncCommunity *protobuf.SyncInstallationCommunity, statusMessage *v1protocol.StatusMessage) error { func (m *Messenger) handleSyncInstallationCommunity(messageState *ReceivedMessageState, syncCommunity *protobuf.SyncInstallationCommunity, statusMessage *v1protocol.StatusMessage) error {
logger := m.logger.Named("handleSyncCommunity") logger := m.logger.Named("handleSyncInstallationCommunity")
// Should handle community // Should handle community
shouldHandle, err := m.communitiesManager.ShouldHandleSyncCommunity(syncCommunity) shouldHandle, err := m.communitiesManager.ShouldHandleSyncCommunity(syncCommunity)
@ -3008,8 +3010,17 @@ func (m *Messenger) handleSyncInstallationCommunity(messageState *ReceivedMessag
return err return err
} }
// This is our own message, so we can trust the set community owner
// This is good to do so that we don't have to queue all the actions done after the handled community description.
// `signer` is `communityID` for a community with no owner token and `owner public key` otherwise
signer, err := utils.RecoverKey(&amm)
if err != nil {
logger.Debug("failed to recover community description signer", zap.Error(err))
return err
}
// TODO: handle shard // TODO: handle shard
err = m.handleCommunityDescription(messageState, orgPubKey, &cd, syncCommunity.Description, nil) err = m.handleCommunityDescription(messageState, signer, &cd, syncCommunity.Description, signer, nil)
if err != nil { if err != nil {
logger.Debug("m.handleCommunityDescription error", zap.Error(err)) logger.Debug("m.handleCommunityDescription error", zap.Error(err))
return err return err
@ -3031,22 +3042,6 @@ func (m *Messenger) handleSyncInstallationCommunity(messageState *ReceivedMessag
} }
} }
savedCommunity, err := m.communitiesManager.GetByID(syncCommunity.Id)
if err != nil {
return err
}
// TODO: if the community is token gated, it will be validated asynchronously
// syncing needs to be adjusted in this case
if savedCommunity == nil {
return nil
}
if err := m.handleCommunityTokensMetadataByPrivilegedMembers(savedCommunity); err != nil {
logger.Debug("m.handleCommunityTokensMetadataByPrivilegedMembers", zap.Error(err))
return err
}
// if we are not waiting for approval, join or leave the community // if we are not waiting for approval, join or leave the community
if !pending { if !pending {
var mr *MessengerResponse var mr *MessengerResponse
@ -3102,10 +3097,6 @@ func (m *Messenger) HandleSyncCommunitySettings(messageState *ReceivedMessageSta
return nil return nil
} }
func (m *Messenger) handleCommunityTokensMetadataByPrivilegedMembers(community *communities.Community) error {
return m.communitiesManager.HandleCommunityTokensMetadataByPrivilegedMembers(community)
}
func (m *Messenger) InitHistoryArchiveTasks(communities []*communities.Community) { func (m *Messenger) InitHistoryArchiveTasks(communities []*communities.Community) {
m.communitiesManager.LogStdout("initializing history archive tasks") m.communitiesManager.LogStdout("initializing history archive tasks")

View File

@ -2324,7 +2324,7 @@ func (m *Messenger) handleChatMessage(state *ReceivedMessageState, forceSeen boo
return err return err
} }
err = m.handleCommunityDescription(state, signer, description, receivedMessage.GetCommunity(), receivedMessage.GetShard()) err = m.handleCommunityDescription(state, signer, description, receivedMessage.GetCommunity(), nil, receivedMessage.GetShard())
if err != nil { if err != nil {
return err return err
} }
@ -3667,7 +3667,7 @@ func (m *Messenger) HandlePushNotificationRequest(state *ReceivedMessageState, m
func (m *Messenger) HandleCommunityDescription(state *ReceivedMessageState, message *protobuf.CommunityDescription, statusMessage *v1protocol.StatusMessage) error { func (m *Messenger) HandleCommunityDescription(state *ReceivedMessageState, message *protobuf.CommunityDescription, statusMessage *v1protocol.StatusMessage) error {
// TODO: handle shard // TODO: handle shard
err := m.handleCommunityDescription(state, state.CurrentMessageState.PublicKey, message, statusMessage.EncryptionLayer.Payload, nil) err := m.handleCommunityDescription(state, state.CurrentMessageState.PublicKey, message, statusMessage.EncryptionLayer.Payload, nil, nil)
if err != nil { if err != nil {
m.logger.Warn("failed to handle CommunityDescription", zap.Error(err)) m.logger.Warn("failed to handle CommunityDescription", zap.Error(err))
return err return err