Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement users relationship #168

Closed
RiccardoM opened this issue May 19, 2020 · 4 comments · Fixed by #260
Closed

Implement users relationship #168

RiccardoM opened this issue May 19, 2020 · 4 comments · Fixed by #260
Assignees
Labels
kind/new-feature Propose the addition of a new feature that does not yet exist status/specification This feature is currently in the specification process x/profiles Module that allows to create and manage decentralized social profiles
Milestone

Comments

@RiccardoM
Copy link
Contributor

RiccardoM commented May 19, 2020

Feature description

In all the social networks currently existing there is a concept that bonds two users between them in a tighter relationship than the rest of the users. For Facebook, this is called "friendship", for Twitter it is called "followage", for YouTube it is called "subscription". Currently inside Desmos we do not have the way of expressing this relationship and we should definitely implement it.

Implementation proposal

One thing to not about all the above mentioned relationship is that they can be either mono- or bi-directional. Inside Twitter when you follow a user, you are the one observing his actions, and the same happens on YouTube. On Facebook, on the other hand, "friendship" expresses a mutual connection, where two friends can observe the action of one another.

Types implementation

In order to properly implement both of these concepts, I think we could define a new object as follows:

// Relationship represents a single relationship between two users. Sender is the one that first
// sent the relationship request, and Receiver is the one that received it and (optionally) accepted it.
type Relationship interface { 
  Sender()   sdk.AccAddress
  Receiver() sdk.AccAddress
}

// MonodirectionalRelationship implements Relationship and represents a monodirectional 
// relationships that does not require the receiver to accept it before being effective. 
type MonodirectionalRelationship struct {
  Sender   sdk.AccAddress   `json:"sender" yaml:"sender"`
  Receiver sdk.AccAddress   `json:"receiver" yaml:"receiver"`
}

// BidirectionalRelationship implements Relationship and represents a bidirectional relationship
// that can have different statuses and requires the receiver to accept it before becoming effective.
type BidirectionalRelationship struct {
  Sender   sdk.AccAddress     `json:"sender" yaml:"sender"`
  Receiver sdk.AccAddress     `json:"receiver" yaml:"receiver"`
  Status   RelationshipStatus `json:"status" yaml:"status"`
}

// RelationshipStatus represents the status of a bidirectional relationship
type RelationshipStatus int

const (
  RelationshipStatus Sent     = 0 // Indicates that the relationship has been sent but not yet accepted or denied
  RelationshipStatus Accepted = 1 // Tells that the relationships has been accepted by the receiver
  RelationshipStatus Denied   = 2 // Tells that the relationship has been denied from the receiver
)

Messages implementation

The operations that two users should be able to perform are distinct based on the relationship type they want to create.

Monodirectional relationships

The only operation that should be sufficient for monodirectional relationships are the creation of the relationship itself. For this the following message should be sufficient:

// Creates a monodirectional relationship (type MonoDirectional = 0) between the sender and 
// the receiver. Monodirectional relationship do not need the receiver to accept in order to be valid.
// An example of monodirectional relationship is the follow on Twitter or the subscribe on YouTube.
type MsgCreateMonoDirectionalRelationship struct {
  Sender   sdk.AccAddress   `json:"sender" yaml:"sender"`
  Receiver sdk.AccAddress   `json:"receiver" yaml:"receiver"`
}

Bidirectional relationship

For bidirectional relationships, due to the fact that the receiver needs to accept, the following set of operations should be defined:

  1. Send the relationship request.
  2. Accept a relationship request or
  3. Deny a relationship request.

The following messages should allow for all of them:

// MsgRequestBidirectionalRelationship allows the specified Sender to ask for a bidirectional
// relationship to the specified Receiver, attaching the given (optional) message to the request.
// In order to accept, the Receiver must be user either MsgAcceptBidirectionalRelationship or
// MsgDenyBidirectionalRelationship.
type MsgRequestBidirectionalRelationship struct {
  Sender   sdk.AccAddress   `json:"sender" yaml:"sender"`
  Receiver sdk.AccAddress   `json:"receiver" yaml:"receiver"`
  Message  string           `json:"message,omitempty" yaml: "message,omitempty"`
}

// MsgAcceptBidirectionalRelationship allows the receiver of a bidirectional relationship request
// to accept that request leading to the effective creation of the relationship itself. 
type MsgAcceptBidirectionalRelationship struct {
  Receiver sdk.AccAddress   `json:"receiver" yaml:"receiver"`
}

// MsgDenyBidirectionalRelationship allows the receiver of a bidirectional relationship request
// to deny that request. 
type MsgDenyBidirectionalRelationship struct {
    Receiver sdk.AccAddress   `json:"receiver" yaml:"receiver"`
}

Two things should be considered about the working of bidirectional relationships:

  1. Once accepted, a request cannot be denied anymore. However, it can still be cut (see below).
  2. Once denied, a request can still be accepted in the future. This will allow platforms similar to Facebook to implement the "Ignore" feature.

Common operations

For both mono- and bi- directional relationship, once the relationship has been created the users taking part to it should be able to cut it off. The difference are:

  • In monodirectional relationships, the cut off can be done only by the Sender of the request.
  • In bidirectional relationships, the cut off can be done by either one of the two participants.

In order to cut off a relationship the following message should be sufficient:

// MsgDeleteRelationships allows the specified User to cut off the relationship he had prevously
// created with the specified Counterparty. 
// If the relationship was a monodirectional relationship, the user must be the original Sender of 
// the relationship, otherwise, if it was a bidirectional one, it can be either one of the two users 
// taking part to it.
type MsgDeleteRelationships struct {
  User         sdk.AccAddress `json:"user" yaml:"user"`
  Counterparty sdk.AccAddress `json:"counterparty" yaml:"counterparty"`
}

Keeper implementation

In order to support all the above mentioned, I think that the following keeper method should be enough:

// SaveRelationship allows to store the given relationship returning an error if something goes wrong.
func (k Keeper) SaveRelationship(ctx sdk.Context, relationship types.Relationship) error {}

// DeleteRelationship allows to delete the relationship between the given user and his counterparty
func (k Keeper) DeleteRelationship(ctx sdk.Context, user, counterparty sdk.AccAddress) error {}

// GetUserRelationships allows to list all the relationships that involve the given user.
func (k Keeper) GetUserRelationships(ctx sdk.Context, user sdk.AccAddress) []types.Relationship {}

Conclusion

@kwunyeung @bragaz let me know what you guys think about this proposal.

@RiccardoM RiccardoM added kind/new-feature Propose the addition of a new feature that does not yet exist status/specification This feature is currently in the specification process x/profiles Module that allows to create and manage decentralized social profiles labels May 19, 2020
@RiccardoM RiccardoM changed the title Implement 1-to-1 users relationship Implement users relationship May 19, 2020
@kwunyeung
Copy link
Contributor

I have been thinking of this recently but I don't know if this should be done on protocol or dapp level. If this is on protocol level, does it mean if A follows B on Mooncake, A will automatically follow B on the other dapps?

@leobragaz
Copy link
Contributor

leobragaz commented May 19, 2020

If this is on protocol level, does it mean if A follows B on Mooncake, A will automatically follow B on the other dapps?

I think so, maybe to fix this we could add an extra subspace field to identify where that relation has been made.

@RiccardoM
Copy link
Contributor Author

RiccardoM commented May 19, 2020

does it mean if A follows B on Mooncake, A will automatically follow B on the other dapps?

I think that this should be left to the dApps themselves. What I image will happen is the following scenario.

Suppose that we have the following relationships:

  1. Alice follows Bob on Mooncake, but Bob does not follow Alice (Alice ➡️ Bob)
  2. Alice follows Carl on Mooncake, and Carl follows Alice back (Alice ↔️ Carl)

Now, suppose that Alice joins a new dApp based on Desmos. What I image it will happen is that the dApp will see the existing relationships on Desmos, and asks Alice herself to choose:

  • "We've detected you follow Bob con Mooncake, and Bob is using this app as well. Would you like to follow him here as well?"
  • "We've detected you and Carl follow each other on Mooncake. Would you like to send him a friend request here?"

This way we have a sort of double definition of relationship, one at the protocol level (which identifies the fact that somewhere, in some dApp, the two users have a connection) and another one on the dApp level which identifies how that relationship should act. The only thing is that the first dApp to create a relationship on its level should also create it at the protocol level.

The important thing to note is that the fact that some data is stored on-chain does not define how apps will use it as they will be free to use it (or not) as they wish.

@RiccardoM RiccardoM modified the milestone: v0.9.0 Jun 18, 2020
@RiccardoM RiccardoM added this to the v0.11.0 milestone Jul 15, 2020
@RiccardoM
Copy link
Contributor Author

RiccardoM commented Aug 24, 2020

After discussing with @bragaz on a call, we decided that is better to drop the bidirectional relationship implementation.

The main influencing factor behind this decision is that they actually represent something only inside the social networks that already have this concept. At the same time, Desmos is a protocol and should have only the bare minimum features to implement all the other ones instead of having more complex ones.

Having only mono-directional relationships is enough to represent also bi-directional ones.

Also, this allows dApp developers to handle the two-way relationship the way they think is better for their social networks. Something like Facebook might want to add another layer of confirmation before creating the connection, while something like Twitter might prefer not to do so. This is a design choice that should not be taken from us, but from people using Desmos instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind/new-feature Propose the addition of a new feature that does not yet exist status/specification This feature is currently in the specification process x/profiles Module that allows to create and manage decentralized social profiles
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants