Build Your Own Decentralized Twitter, Part 3: Hello Mastodon

In Part 1 of this series, we created an architecture for a distributed social media system, using a Visual Studio code project and corresponding JSON files. In Part 2, we figured out how to do mitigations — addressing problems like deleted tweets.
We saw in the previous post that our distributed project system was user-centered but very unruly. What we need to do now, in Part 3, is trade a little control for a lot more certainty. That means looking into Mastodon’s federated social media approach.
In a federated model, servers talk to each other to build up the social graph. Thus a Mastodon server is simply a server that complies with the appropriate protocols and takes its part in a federation. A user joins a server, and in turn takes part in that federation. These servers have their own reasons to exist, but typically it’s to represent a particular community’s concerns.
We can see that the federation model is a little like our project model, but with the tweet stores sitting with one of the tweet view servers. From the server’s point of view, there are now local tweets and outer tweets. Let’s see how federated servers improve on the weaknesses of our projects fully “user-centered” model.
Identity
In our project, there was no useful way of uniquely identifying anything (tweeter or tweet viewer), but that was part and parcel of a “tweeter-centered” system. While an identity is not as absolute as it is on Twitter, federated servers do have identities. Although Mastodon claims to have no central authority, servers are unique in the sense of their internet domain’s uniqueness. This means that a user on Mastodon has an identity, because a user is associated with one server. My address might well be @eastmad@mastodon.social (I don’t remember what it actually is), and this has a good chance of being both unique and secure. I note there is a “Mastodon Server Covenant,” which is an inevitable attempt to have some central guidance above the possible chaos. The bottom line is that by giving some trust to the server, we gain some advantages.
Structure
In our model, access to a tweeter’s tweets was controlled by the tweeter, so conversations could disappear at any time. With a federation, servers communicate amongst themselves to build the graph, so the graph is not at the whim of individuals in quite the same way. Mastodon servers often map to real-world communities, like Mastodon.cat for Catalan speakers, so users might have a pre-existing commonality. While iterating through the graph is somewhat costly, as we’ll see later, the server is in control of a valid source of truth. So unlike with our project, the Mastodon model doesn’t need to consider mitigations to maintain stability.
Content
We saw in our project model that because the tweeter controlled their content (and thus was the source of truth), not only could you tweet anything, you could also edit anything at any time. With a federated model, the content is owned by the server. Servers independently decide what their policies are. Unsurprisingly, most do what they can to ban hate speech, for example.
Freedom of expression does survive federation, but that freedom is Balkanized. So you can indeed join a server that allows you to express your deeply unpopular thoughts, but you may find that your foul conversations only exist on a very small branch of the social graph because other servers don’t talk to your server. This emphasizes the “social bubble” or “safe space”, which may at different times be seen as a boon or a drawback.
Here Comes the Mastodon
If you followed along with the project code, you will be ready to see how the somewhat more comprehensive code behind Mastodon deals with conversation. How would we alter our model to work with the Mastodon API?
Our project only threaded messages in a simplistic way, because it had no idea about ancestors and descendants. It just did a quick check on the sorted list before displaying a tweet, if the last message id had the same id as the reply-to field in the current message:
1 2 3 4 5 6 7 8 9 10 11 12 |
long prevtweetid = -1; char sepchar = ':'; foreach (var tweetfrom in totalTweets) { if (prevtweetid == tweetfrom.tweet.Replyto) sepchar = '↳'; else sepchar = '-'; Console.WriteLine($".. {sepchar} {tweetfrom.tweet.Text} "); prevtweetid = tweetfrom.tweet.Time; } |
Clearly, a post could be a reply to any previous post.
For full functionality, we have to know — for each post — which is the parent post (there can be only one, or none), and how many children it has (none or many).
In Mastodon, a post is called a status. Remember, when Twitter started in 2006, it invited intrepid pioneers to respond to the statement “What Are You Doing?”, because at that point it wasn’t designed to be used for conversation: it was more of a diary status.
Each status has an id. As in our project, the id is probably based on a Unix timestamp. And just like in our project, there is a in_reply_to_id that stores the id of the status it is replying to. To simply fetch a status with the API, you use the status id:
1 |
/api/v1/statuses/1 |
We could replicate this easily enough in our project, but we would need a user. Unlike in a truly distributed system, in a federated system the tweet (or status) is central — not the user.
Mastodon’s API calls a conversation a context. (This is what happens when you let developers name things!) Let’s take our existing example conversation with Alan, Beth and Cath, and think of it in the Mastodon model:
Alan’s first status would have an id of 1668435369. To get at the conversation, the API call would be:
1 |
/api/v1/statuses/1668435369/context |
Alan started the conversation, so the status has no ancestors and only one descendant. If we called the API on the one descendant (1668435540), we would discover the single descendant (1668438963), etc.
Mastodon context calls only return these two lists, ancestor ids and descendant ids, but after a few calls our entire conversation would be “parsed” — and it would do a much more comprehensive job of modelling the conversation than our project.
You should already see that if you wanted to investigate a complicated conversation, you would immediately become a tree surgeon, having to go up and down branches to count all the leaves. Eventually, you come up to the trunk, find all the major branches, and can at least be sure you have the full scope.
As you can see from this graph, wherever you started you would need a good number of Mastodon context API calls (about nine) to model the whole tree with only 11 nodes:
The Twitter Kingdom vs. the Federation
Ultimately, the two contenders for the “public conversation” social media crown would appear to be the monolith Twitter and the decentralized Mastodon. Hopefully, I’ve shown that they are quite dissimilar, with the Twitter Kingdom presenting an entire empire in a box, while the federation can grow or shrink based on the real-world communities that want to take part.
As we’ve seen recently, a Kingdom is dependent on a wise ruler — and suffers without one. A federation nurtures healthy communities much more than the lone individual. Both ideas are valid, and in my view, it is more appropriate to celebrate the good features of both rather than wanting only one to dominate.